Initial commit
This commit is contained in:
commit
c534d5ce80
163 files changed
+11500
No files matched your search
@@ -0,0 +1,369 @@
|
||||
// ==========================================
|
||||
// ПЕРЕИСПОЛЬЗУЕМЫЕ КОМПОНЕНТЫ
|
||||
// ==========================================
|
||||
#import "presentation/lists.typ": resolve-list-layout
|
||||
|
||||
// Корпоративная таблица с оранжевой шапкой.
|
||||
// Для простых таблиц первая строка по-прежнему считается шапкой.
|
||||
// Для сложной шапки используйте `header: (...)`.
|
||||
#let corp_table(
|
||||
columns: auto,
|
||||
table-align: center, // center | left | right
|
||||
row_breakable: true,
|
||||
page_break: "fit",
|
||||
text-size: 12pt,
|
||||
leading: 0.65em,
|
||||
header-leading: auto,
|
||||
body-leading: auto,
|
||||
spacing: auto,
|
||||
header-spacing: auto,
|
||||
body-spacing: auto,
|
||||
justify: auto,
|
||||
first-line-indent: 0pt,
|
||||
hyphenate: auto,
|
||||
header-hyphenate: auto,
|
||||
body-hyphenate: auto,
|
||||
inset: 0% + 5pt,
|
||||
header-inset: auto,
|
||||
body-inset: auto,
|
||||
list-layout: auto,
|
||||
list-indent: auto,
|
||||
list-body-indent: auto,
|
||||
header: auto,
|
||||
body: auto,
|
||||
repeat_header: true,
|
||||
header_fill: rgb("ffd35f"),
|
||||
continuation: true,
|
||||
continuation_text: "Продолжение таблицы",
|
||||
continuation_with_number: true,
|
||||
continuation_gap: 0.5em,
|
||||
..args,
|
||||
) = {
|
||||
let resolve-table-align = value => {
|
||||
if value == left or value == "left" {
|
||||
left
|
||||
} else if value == right or value == "right" {
|
||||
right
|
||||
} else {
|
||||
center
|
||||
}
|
||||
}
|
||||
let block-align = resolve-table-align(table-align)
|
||||
|
||||
let cols_count = if type(columns) == int {
|
||||
columns
|
||||
} else if type(columns) == array {
|
||||
columns.len()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
|
||||
let pos_args = args.pos()
|
||||
let named_args = args.named()
|
||||
|
||||
let normalize-cells = value => {
|
||||
if value == auto or value == none {
|
||||
()
|
||||
} else if type(value) == array {
|
||||
value
|
||||
} else {
|
||||
(value,)
|
||||
}
|
||||
}
|
||||
|
||||
let span-of = (item, key) => {
|
||||
if type(item) == content and item.func() == table.cell {
|
||||
item.at(key, default: 1)
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
let decay-carries = carries => range(carries.len()).map(i => calc.max(carries.at(i) - 1, 0))
|
||||
|
||||
let advance-slot(row, col, carries) = {
|
||||
if col >= cols_count {
|
||||
advance-slot(row + 1, 0, decay-carries(carries))
|
||||
} else if carries.at(col) > 0 {
|
||||
advance-slot(row, col + 1, carries)
|
||||
} else {
|
||||
(row, col, carries)
|
||||
}
|
||||
}
|
||||
|
||||
let update-carries = (carries, col, colspan, rowspan) => {
|
||||
range(carries.len()).map(i => if i >= col and i < col + colspan { calc.max(carries.at(i), rowspan) } else { carries.at(i) })
|
||||
}
|
||||
|
||||
let update-filled = (filled, row, colspan, rowspan) => {
|
||||
range(filled.len()).map(i => if i >= row and i < row + rowspan { filled.at(i) + colspan } else { filled.at(i) })
|
||||
}
|
||||
|
||||
let rows-filled(filled, row-count) = {
|
||||
if row-count <= 0 {
|
||||
true
|
||||
} else if filled.at(row-count - 1) == cols_count {
|
||||
rows-filled(filled, row-count - 1)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let infer-header-rows(cells, idx: 0, row: 0, col: 0, carries: range(cols_count).map(_ => 0), max-row: -1) = {
|
||||
if idx >= cells.len() {
|
||||
calc.max(max-row + 1, 0)
|
||||
} else {
|
||||
let item = cells.at(idx)
|
||||
let slot = advance-slot(row, col, carries)
|
||||
let place-row = slot.at(0)
|
||||
let place-col = slot.at(1)
|
||||
let place-carries = slot.at(2)
|
||||
let colspan = span-of(item, "colspan")
|
||||
let rowspan = span-of(item, "rowspan")
|
||||
let next-carries = update-carries(place-carries, place-col, colspan, rowspan)
|
||||
infer-header-rows(
|
||||
cells,
|
||||
idx: idx + 1,
|
||||
row: place-row,
|
||||
col: place-col + colspan,
|
||||
carries: next-carries,
|
||||
max-row: calc.max(max-row, place-row + rowspan - 1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let infer-auto-header-len(
|
||||
cells,
|
||||
idx: 0,
|
||||
row: 0,
|
||||
col: 0,
|
||||
carries: range(cols_count).map(_ => 0),
|
||||
filled: none,
|
||||
first-row-max: 1,
|
||||
header-rows: none,
|
||||
) = {
|
||||
let filled = if filled == none { range(calc.max(cells.len(), 1) + 2).map(_ => 0) } else { filled }
|
||||
if idx >= cells.len() {
|
||||
cells.len()
|
||||
} else {
|
||||
let item = cells.at(idx)
|
||||
let slot = advance-slot(row, col, carries)
|
||||
let place-row = slot.at(0)
|
||||
let place-col = slot.at(1)
|
||||
let place-carries = slot.at(2)
|
||||
let colspan = span-of(item, "colspan")
|
||||
let rowspan = span-of(item, "rowspan")
|
||||
let next-carries = update-carries(place-carries, place-col, colspan, rowspan)
|
||||
let next-filled = update-filled(filled, place-row, colspan, rowspan)
|
||||
let next-first-row-max = if place-row == 0 { calc.max(first-row-max, rowspan) } else { first-row-max }
|
||||
let next-header-rows = if header-rows == none and next-filled.at(0) == cols_count { next-first-row-max } else { header-rows }
|
||||
|
||||
if next-header-rows != none and rows-filled(next-filled, next-header-rows) {
|
||||
idx + 1
|
||||
} else {
|
||||
infer-auto-header-len(
|
||||
cells,
|
||||
idx: idx + 1,
|
||||
row: place-row,
|
||||
col: place-col + colspan,
|
||||
carries: next-carries,
|
||||
filled: next-filled,
|
||||
first-row-max: next-first-row-max,
|
||||
header-rows: next-header-rows,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let header_cells = if header == auto {
|
||||
let h_len = infer-auto-header-len(pos_args)
|
||||
pos_args.slice(0, h_len)
|
||||
} else {
|
||||
normalize-cells(header)
|
||||
}
|
||||
|
||||
let body_cells = if body == auto {
|
||||
if header == auto {
|
||||
pos_args.slice(header_cells.len())
|
||||
} else {
|
||||
pos_args
|
||||
}
|
||||
} else {
|
||||
normalize-cells(body)
|
||||
}
|
||||
|
||||
let header_row_count = if header_cells.len() > 0 { infer-header-rows(header_cells) } else { 0 }
|
||||
let has_header = header_row_count > 0
|
||||
if "align" not in named_args {
|
||||
// Центрирование самой таблицы не должно наследоваться текстом ячеек.
|
||||
// Шапка по умолчанию центрируется, содержимое читается слева направо.
|
||||
named_args.insert(
|
||||
"align",
|
||||
(x, y) => if has_header and y >= 1 and y <= header_row_count {
|
||||
center + horizon
|
||||
} else {
|
||||
left + top
|
||||
},
|
||||
)
|
||||
}
|
||||
if type(row_breakable) != bool {
|
||||
panic("Scientia / corp-table.row-breakable: ожидался bool")
|
||||
}
|
||||
let cell_breakable = if type(page_break) == bool {
|
||||
page_break
|
||||
} else if page_break == "fit" {
|
||||
row_breakable
|
||||
} else if page_break == "cell" {
|
||||
false
|
||||
} else {
|
||||
panic("Scientia / corp-table.page-break: ожидались bool, 'fit' или 'cell'")
|
||||
}
|
||||
let resolved-header-leading = if header-leading == auto { leading } else { header-leading }
|
||||
let resolved-body-leading = if body-leading == auto { leading } else { body-leading }
|
||||
let resolved-header-spacing = if header-spacing == auto { spacing } else { header-spacing }
|
||||
let resolved-body-spacing = if body-spacing == auto { spacing } else { body-spacing }
|
||||
let resolved-header-hyphenate = if header-hyphenate == auto { hyphenate } else { header-hyphenate }
|
||||
let resolved-body-hyphenate = if body-hyphenate == auto { hyphenate } else { body-hyphenate }
|
||||
let resolved-header-inset = if header-inset == auto { inset } else { header-inset }
|
||||
let resolved-body-inset = if body-inset == auto { inset } else { body-inset }
|
||||
let resolved-list-layout = resolve-list-layout(
|
||||
list-layout,
|
||||
kind: "list",
|
||||
level-indent: list-indent,
|
||||
body-indent: list-body-indent,
|
||||
)
|
||||
let resolved-enum-layout = resolve-list-layout(
|
||||
list-layout,
|
||||
kind: "enum",
|
||||
level-indent: list-indent,
|
||||
body-indent: list-body-indent,
|
||||
)
|
||||
let list-options = (:)
|
||||
let enum-options = (:)
|
||||
if resolved-list-layout.level-indent != auto {
|
||||
list-options.insert("indent", resolved-list-layout.level-indent)
|
||||
}
|
||||
if resolved-list-layout.body-indent != auto {
|
||||
list-options.insert("body-indent", resolved-list-layout.body-indent)
|
||||
}
|
||||
if resolved-enum-layout.level-indent != auto {
|
||||
enum-options.insert("indent", resolved-enum-layout.level-indent)
|
||||
}
|
||||
if resolved-enum-layout.body-indent != auto {
|
||||
enum-options.insert("body-indent", resolved-enum-layout.body-indent)
|
||||
}
|
||||
|
||||
let cont_cell = table.cell(colspan: cols_count, stroke: none, fill: none, align: left, inset: 0pt)[
|
||||
#context {
|
||||
if not continuation {
|
||||
v(0pt)
|
||||
} else {
|
||||
let current_page = counter(page).get().first()
|
||||
let tables_before = query(selector(table).before(here()))
|
||||
let start_page = if tables_before == () {
|
||||
current_page
|
||||
} else {
|
||||
counter(page).at(tables_before.last().location()).first()
|
||||
}
|
||||
|
||||
if current_page > start_page {
|
||||
let continuation_label = if type(continuation_text) == str {
|
||||
text(continuation_text.replace(" ", " "))
|
||||
} else {
|
||||
continuation_text
|
||||
}
|
||||
let continuation_title = if continuation_with_number {
|
||||
[#continuation_label #counter(figure.where(kind: table)).display()]
|
||||
} else {
|
||||
continuation_label
|
||||
}
|
||||
set text(weight: "regular", size: text-size, hyphenate: false)
|
||||
pad(bottom: continuation_gap)[#box(continuation_title)]
|
||||
} else {
|
||||
v(0pt)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
set text(size: text-size)
|
||||
set table.cell(breakable: cell_breakable)
|
||||
|
||||
show table.cell: it => {
|
||||
let is-header = has_header and it.y >= 1 and it.y <= header_row_count
|
||||
let current-leading = if is-header { resolved-header-leading } else { resolved-body-leading }
|
||||
let current-spacing = if is-header { resolved-header-spacing } else { resolved-body-spacing }
|
||||
let current-hyphenate = if is-header { resolved-header-hyphenate } else { resolved-body-hyphenate }
|
||||
|
||||
// Настройка принадлежит таблице и наследуется списками в её ячейках.
|
||||
// Локальная оболочка bullet-list/numbered-list может переопределить её.
|
||||
set list(..list-options)
|
||||
set enum(..enum-options)
|
||||
|
||||
// Важно: block(width:100%, it) ломает align: ... + horizon (вертикальное центрирование).
|
||||
// Решение: возвращаем `it` напрямую, а все set-правила применяем через вложенные if-else,
|
||||
// где `it` находится ВНУТРИ той же ветки, что и set — иначе set не распространяется на it.
|
||||
// spacing применяется снаружи через замыкание, чтобы не дублировать внутренние ветки.
|
||||
set par(leading: current-leading)
|
||||
|
||||
let styled-it = () => {
|
||||
if is-header {
|
||||
set par(first-line-indent: 0pt)
|
||||
set text(weight: "bold")
|
||||
if justify != auto {
|
||||
set par(justify: justify)
|
||||
if current-hyphenate != auto { set text(hyphenate: current-hyphenate); it } else { it }
|
||||
} else if current-hyphenate != auto { set text(hyphenate: current-hyphenate); it } else { it }
|
||||
} else if first-line-indent != auto {
|
||||
set par(first-line-indent: first-line-indent)
|
||||
if justify != auto {
|
||||
set par(justify: justify)
|
||||
if current-hyphenate != auto { set text(hyphenate: current-hyphenate); it } else { it }
|
||||
} else if current-hyphenate != auto { set text(hyphenate: current-hyphenate); it } else { it }
|
||||
} else {
|
||||
if justify != auto {
|
||||
set par(justify: justify)
|
||||
if current-hyphenate != auto { set text(hyphenate: current-hyphenate); it } else { it }
|
||||
} else if current-hyphenate != auto { set text(hyphenate: current-hyphenate); it } else { it }
|
||||
}
|
||||
}
|
||||
|
||||
if current-spacing != auto {
|
||||
set par(spacing: current-spacing)
|
||||
styled-it()
|
||||
} else {
|
||||
styled-it()
|
||||
}
|
||||
}
|
||||
|
||||
align(block-align)[
|
||||
#table(
|
||||
columns: columns,
|
||||
stroke: 0.5pt,
|
||||
fill: (x, y) => if has_header and y >= 1 and y <= header_row_count { header_fill } else { none },
|
||||
inset: (x, y) => if has_header and y >= 1 and y <= header_row_count { resolved-header-inset } else { resolved-body-inset },
|
||||
..named_args,
|
||||
..if has_header {
|
||||
(
|
||||
table.header(
|
||||
repeat: repeat_header,
|
||||
cont_cell,
|
||||
..header_cells,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
()
|
||||
},
|
||||
..body_cells
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
// Объект формулы по ГОСТ:
|
||||
// центрирование формулы и номер справа настраиваются глобально в report.typ.
|
||||
// Использование: #formula($ ... $) <eq_label>
|
||||
#let formula(body, ..args) = math.equation(
|
||||
body,
|
||||
block: true,
|
||||
..args,
|
||||
)
|
||||
Reference in new issue
Block a user