mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-03-11 17:49:25 -05:00
feat: add standalone PDF build infrastructure for Kits
Created independent build system for Hardware Kits PDF output: - Added PDF profile (_quarto/profiles/pdf/_quarto.yml) - Copied tex files with teal branding (#0d9488) - Added inject_parts.lua filter for part summaries - Updated Makefile with html and pdf targets Usage: make pdf (or quarto render --profile pdf)
This commit is contained in:
@@ -4,17 +4,24 @@
|
||||
# Part of the MLSysBook ecosystem
|
||||
# =============================================================================
|
||||
|
||||
.PHONY: all build preview clean help
|
||||
.PHONY: all build html pdf preview clean help
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
all: html
|
||||
|
||||
# Build the kits site
|
||||
# Build HTML site (default)
|
||||
html: build
|
||||
build:
|
||||
@echo "🔧 Building Hardware Kits site..."
|
||||
@echo "🔧 Building Hardware Kits HTML site..."
|
||||
quarto render
|
||||
@echo "✅ Build complete: _build/"
|
||||
|
||||
# Build PDF
|
||||
pdf:
|
||||
@echo "📄 Building Hardware Kits PDF..."
|
||||
quarto render --profile pdf
|
||||
@echo "✅ PDF build complete: _build/pdf/"
|
||||
|
||||
# Preview the site locally
|
||||
preview:
|
||||
@echo "👀 Starting preview server..."
|
||||
@@ -32,7 +39,9 @@ help:
|
||||
@echo "Hardware Kits Build System"
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@echo " make build - Build the site to _build/"
|
||||
@echo " make build - Build HTML site to _build/"
|
||||
@echo " make html - Build HTML site to _build/ (same as build)"
|
||||
@echo " make pdf - Build PDF to _build/pdf/"
|
||||
@echo " make preview - Start local preview server"
|
||||
@echo " make clean - Remove build artifacts"
|
||||
@echo " make help - Show this help message"
|
||||
|
||||
103
kits/_quarto/profiles/pdf/_quarto.yml
Normal file
103
kits/_quarto/profiles/pdf/_quarto.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
# =============================================================================
|
||||
# HARDWARE KITS PDF PROFILE
|
||||
# =============================================================================
|
||||
# This profile builds the Hardware Kits labs as a standalone PDF.
|
||||
# Usage: quarto render --profile pdf
|
||||
# =============================================================================
|
||||
|
||||
project:
|
||||
type: book
|
||||
output-dir: _build/pdf
|
||||
|
||||
book:
|
||||
title: "Hardware Kits"
|
||||
subtitle: "Hands-On Embedded ML Labs"
|
||||
author: "Marcelo Rovai with Prof. Vijay Janapa Reddi"
|
||||
date: today
|
||||
|
||||
chapters:
|
||||
# Frontmatter
|
||||
- index.qmd
|
||||
- contents/getting-started.qmd
|
||||
- contents/platforms.qmd
|
||||
- contents/ide-setup.qmd
|
||||
|
||||
# Arduino Labs
|
||||
- part: "Arduino Nicla Vision"
|
||||
chapters:
|
||||
- contents/arduino/nicla_vision/nicla_vision.qmd
|
||||
- contents/arduino/nicla_vision/setup/setup.qmd
|
||||
- contents/arduino/nicla_vision/image_classification/image_classification.qmd
|
||||
- contents/arduino/nicla_vision/object_detection/object_detection.qmd
|
||||
- contents/arduino/nicla_vision/kws/kws.qmd
|
||||
- contents/arduino/nicla_vision/motion_classification/motion_classification.qmd
|
||||
|
||||
# Seeed XIAO Labs
|
||||
- part: "Seeed XIAO ESP32S3"
|
||||
chapters:
|
||||
- contents/seeed/xiao_esp32s3/xiao_esp32s3.qmd
|
||||
- contents/seeed/xiao_esp32s3/setup/setup.qmd
|
||||
- contents/seeed/xiao_esp32s3/image_classification/image_classification.qmd
|
||||
- contents/seeed/xiao_esp32s3/object_detection/object_detection.qmd
|
||||
- contents/seeed/xiao_esp32s3/kws/kws.qmd
|
||||
- contents/seeed/xiao_esp32s3/motion_classification/motion_classification.qmd
|
||||
|
||||
# Grove Vision Labs
|
||||
- part: "Grove Vision AI V2"
|
||||
chapters:
|
||||
- contents/seeed/grove_vision_ai_v2/grove_vision_ai_v2.qmd
|
||||
- contents/seeed/grove_vision_ai_v2/setup_and_no_code_apps/setup_and_no_code_apps.qmd
|
||||
- contents/seeed/grove_vision_ai_v2/image_classification/image_classification.qmd
|
||||
- contents/seeed/grove_vision_ai_v2/object_detection/object_detection.qmd
|
||||
|
||||
# Raspberry Pi Labs
|
||||
- part: "Raspberry Pi"
|
||||
chapters:
|
||||
- contents/raspi/raspi.qmd
|
||||
- contents/raspi/setup/setup.qmd
|
||||
- contents/raspi/image_classification/image_classification.qmd
|
||||
- contents/raspi/object_detection/object_detection.qmd
|
||||
- contents/raspi/llm/llm.qmd
|
||||
- contents/raspi/vlm/vlm.qmd
|
||||
|
||||
# Shared Resources
|
||||
- part: "Shared Resources"
|
||||
chapters:
|
||||
- contents/shared/shared.qmd
|
||||
- contents/shared/kws_feature_eng/kws_feature_eng.qmd
|
||||
- contents/shared/dsp_spectral_features_block/dsp_spectral_features_block.qmd
|
||||
|
||||
format:
|
||||
pdf:
|
||||
documentclass: scrbook
|
||||
classoption:
|
||||
- openany
|
||||
- twoside
|
||||
papersize: letter
|
||||
fontsize: 10pt
|
||||
|
||||
# LaTeX includes
|
||||
include-in-header: tex/header-includes.tex
|
||||
include-before-body: tex/before-body-includes.tex
|
||||
include-after-body: tex/after-body-includes.tex
|
||||
|
||||
# PDF engine
|
||||
pdf-engine: lualatex
|
||||
|
||||
# Output settings
|
||||
keep-tex: false
|
||||
toc: true
|
||||
toc-depth: 2
|
||||
number-sections: true
|
||||
|
||||
# Code formatting
|
||||
code-copy: false
|
||||
code-overflow: wrap
|
||||
highlight-style: github
|
||||
|
||||
# Lua filters
|
||||
filters:
|
||||
- filters/inject_parts.lua
|
||||
|
||||
# Metadata for filters
|
||||
part-summaries: contents/parts/summaries.yml
|
||||
454
kits/filters/inject_parts.lua
Normal file
454
kits/filters/inject_parts.lua
Normal file
@@ -0,0 +1,454 @@
|
||||
-- ===============================================================================
|
||||
-- PART STYLING LUA FILTER
|
||||
-- ===============================================================================
|
||||
--
|
||||
-- This filter transforms \part{key:xxx} commands in QMD files into appropriate
|
||||
-- LaTeX commands based on the key name, creating a structured book hierarchy.
|
||||
--
|
||||
-- ROUTING LOGIC:
|
||||
-- 1. Book Divisions (frontmatter, main_content, backmatter, labs) → \division{title}
|
||||
-- - Clean centered styling with geometric background
|
||||
-- - No descriptions displayed
|
||||
--
|
||||
-- 2. Lab Platforms (arduino, xiao, grove, raspberry, shared) → \labdivision{title}
|
||||
-- - Circuit-style neural network design with nodes and connections
|
||||
-- - No descriptions displayed
|
||||
--
|
||||
-- 3. Numbered Parts (foundations, principles, etc.) → \part{title}
|
||||
-- - Roman numeral styling (via \titleformat{\part})
|
||||
-- - Includes part description
|
||||
--
|
||||
-- USAGE IN QMD FILES:
|
||||
-- Simply use \part{key:foundations} and this filter will:
|
||||
-- 1. Look up the key in part_summaries.yml
|
||||
-- 2. Extract title and description
|
||||
-- 3. Generate appropriate LaTeX command
|
||||
-- 4. Include \setpartsummary{description} if needed
|
||||
--
|
||||
-- EXAMPLE TRANSFORMATIONS:
|
||||
-- \part{key:foundations} → \part{Systems Foundations} + description
|
||||
-- \part{key:labs} → \division{Labs} (clean geometric style)
|
||||
-- \part{key:arduino} → \labdivision{Arduino Labs} (circuit-style neural network)
|
||||
-- \part{key:frontmatter} → \division{Frontmatter} (clean geometric style)
|
||||
-- ===============================================================================
|
||||
|
||||
-- 🔧 Normalize keys (lowercase, trim leading/trailing whitespace)
|
||||
local function normalize(str)
|
||||
return str:lower():gsub("^%s+", ""):gsub("%s+$", "")
|
||||
end
|
||||
|
||||
-- 🗝️ Extract key from LaTeX part command
|
||||
local function extract_key_from_latex(content)
|
||||
-- Look for \part{key:xxx} pattern in content
|
||||
local key = content:match("\\part%{key:([^}]+)%}")
|
||||
return key
|
||||
end
|
||||
|
||||
-- Helper function for formatted logging
|
||||
local function log_info(message)
|
||||
io.stderr:write("📄 [Part Summary Filter] " .. message .. "\n")
|
||||
io.stderr:flush()
|
||||
end
|
||||
|
||||
local function log_success(message)
|
||||
io.stderr:write("✅ [Part Summary Filter] " .. message .. "\n")
|
||||
io.stderr:flush()
|
||||
end
|
||||
|
||||
local function log_warning(message)
|
||||
io.stderr:write("⚠️ [Part Summary Filter] " .. message .. "\n")
|
||||
io.stderr:flush()
|
||||
end
|
||||
|
||||
local function log_error(message)
|
||||
io.stderr:write("❌ [Part Summary Filter] " .. message .. "\n")
|
||||
io.stderr:flush()
|
||||
end
|
||||
|
||||
-- Roman numeral conversion
|
||||
local function to_roman(num)
|
||||
local values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
|
||||
local numerals = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
|
||||
local result = ""
|
||||
for i, value in ipairs(values) do
|
||||
while num >= value do
|
||||
result = result .. numerals[i]
|
||||
num = num - value
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Dynamic part numbering system
|
||||
local current_division = nil
|
||||
local part_counter = 0
|
||||
|
||||
-- Reset counter when new division is encountered
|
||||
local function reset_part_counter(division)
|
||||
if current_division ~= division then
|
||||
current_division = division
|
||||
part_counter = 0
|
||||
log_info("🔄 New division encountered: '" .. division .. "' - resetting part counter to 0")
|
||||
end
|
||||
end
|
||||
|
||||
-- Get next part number for current division
|
||||
local function get_next_part_number()
|
||||
part_counter = part_counter + 1
|
||||
return part_counter
|
||||
end
|
||||
|
||||
-- Helper function to format title with part number
|
||||
local function format_part_title(key, title, numbered)
|
||||
-- Always return just the clean title without any "Part X —" prefix
|
||||
-- The LaTeX template handles Roman numerals and "Part X" labels separately
|
||||
return title
|
||||
end
|
||||
|
||||
-- 📄 Read summaries.yml into a Lua table
|
||||
local function read_summaries(path)
|
||||
local summaries = {}
|
||||
|
||||
local file = io.open(path, "r")
|
||||
if not file then
|
||||
log_warning("Failed to open part summaries file: " .. path)
|
||||
return summaries
|
||||
end
|
||||
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
-- Parse YAML manually for the new structure with parts array
|
||||
local entries_loaded = 0
|
||||
local current_key = nil
|
||||
local current_title = nil
|
||||
local current_type = nil
|
||||
local current_numbered = nil
|
||||
local description_lines = {}
|
||||
local in_parts_section = false
|
||||
local in_description = false
|
||||
|
||||
for line in content:gmatch("[^\r\n]+") do
|
||||
if line:match('^parts:') then
|
||||
in_parts_section = true
|
||||
elseif in_parts_section and line:match('%s*%-%s*key:%s*"([^"]+)"') then
|
||||
if current_key and current_title then
|
||||
local description = table.concat(description_lines, " "):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
summaries[normalize(current_key)] = {
|
||||
title = current_title,
|
||||
description = description,
|
||||
type = current_type or "part",
|
||||
numbered = current_numbered or false
|
||||
}
|
||||
entries_loaded = entries_loaded + 1
|
||||
log_info("📝 Loaded: '" .. current_key .. "' → '" .. current_title .. "' (type: " .. (current_type or "part") .. ", numbered: " .. tostring(current_numbered or false) .. ")")
|
||||
end
|
||||
current_key = line:match('%s*%-%s*key:%s*"([^"]+)"')
|
||||
current_title = nil
|
||||
current_type = nil
|
||||
current_numbered = nil
|
||||
description_lines = {}
|
||||
in_description = false
|
||||
elseif in_parts_section and current_key and line:match('%s*title:%s*"([^"]+)"') then
|
||||
current_title = line:match('%s*title:%s*"([^"]+)"')
|
||||
elseif in_parts_section and current_key and line:match('%s*type:%s*"([^"]+)"') then
|
||||
current_type = line:match('%s*type:%s*"([^"]+)"')
|
||||
elseif in_parts_section and current_key and line:match('%s*numbered:%s*(true|false)') then
|
||||
current_numbered = line:match('%s*numbered:%s*(true|false)') == "true"
|
||||
elseif in_parts_section and current_key and line:match('%s*description:%s*>?') then
|
||||
in_description = true
|
||||
elseif in_description and current_key then
|
||||
local desc_content = line:match('%s%s%s*(.+)')
|
||||
if desc_content then
|
||||
table.insert(description_lines, desc_content)
|
||||
elseif line:match('^%s*$') then
|
||||
-- Empty line, continue
|
||||
else
|
||||
-- End of description
|
||||
in_description = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle the last entry
|
||||
if current_key and current_title then
|
||||
local description = table.concat(description_lines, " "):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
summaries[normalize(current_key)] = {
|
||||
title = current_title,
|
||||
description = description,
|
||||
type = current_type or "part",
|
||||
numbered = current_numbered or false
|
||||
}
|
||||
entries_loaded = entries_loaded + 1
|
||||
log_info("📝 Loaded: '" .. current_key .. "' → '" .. current_title .. "' (type: " .. (current_type or "part") .. ", numbered: " .. tostring(current_numbered or false) .. ")")
|
||||
end
|
||||
|
||||
log_success("Successfully loaded " .. entries_loaded .. " part summaries")
|
||||
return summaries
|
||||
end
|
||||
|
||||
-- Load summaries from metadata
|
||||
local has_part_summaries = false
|
||||
local summaries = {}
|
||||
|
||||
-- Validation function to check all keys in the document
|
||||
local function validate_all_keys()
|
||||
if not has_part_summaries then return end
|
||||
|
||||
local used_keys = {}
|
||||
local invalid_keys = {}
|
||||
|
||||
-- Collect all keys used in the document
|
||||
for key, _ in pairs(summaries) do
|
||||
used_keys[key] = true
|
||||
end
|
||||
|
||||
-- Check if any keys are missing from part_summaries.yml
|
||||
for key, _ in pairs(used_keys) do
|
||||
if not summaries[key] then
|
||||
table.insert(invalid_keys, key)
|
||||
end
|
||||
end
|
||||
|
||||
-- If there are invalid keys, report them all at once
|
||||
if #invalid_keys > 0 then
|
||||
log_error("❌ CRITICAL ERROR: Multiple undefined keys found:")
|
||||
for _, key in ipairs(invalid_keys) do
|
||||
log_error(" - '" .. key .. "' not found in part_summaries.yml")
|
||||
end
|
||||
log_error("🔍 Available keys: frontmatter, main_content, foundations, principles, optimization, deployment, trustworthy, futures, labs, arduino, xiao, grove, raspberry, shared, backmatter")
|
||||
log_error("🛑 Build stopped - fix all undefined keys before proceeding")
|
||||
error("Part summary filter failed: multiple undefined keys found. Please check your .qmd files and part_summaries.yml for consistency.")
|
||||
end
|
||||
end
|
||||
|
||||
-- Pre-scan function to validate all keys before processing
|
||||
local function prescan_document_keys(doc)
|
||||
if not has_part_summaries then return end
|
||||
|
||||
log_info("🔍 Pre-scanning document for part keys...")
|
||||
|
||||
local found_keys = {}
|
||||
local invalid_keys = {}
|
||||
local key_locations = {}
|
||||
|
||||
-- Scan all RawBlocks for \part{key:xxx} patterns
|
||||
local function scan_blocks(blocks)
|
||||
for i, block in ipairs(blocks) do
|
||||
if block.t == "RawBlock" and block.format == "latex" then
|
||||
local key = extract_key_from_latex(block.text)
|
||||
if key then
|
||||
local normalized_key = normalize(key)
|
||||
found_keys[normalized_key] = true
|
||||
|
||||
-- Check if key is valid
|
||||
if not summaries[normalized_key] then
|
||||
table.insert(invalid_keys, normalized_key)
|
||||
key_locations[normalized_key] = i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Recursively scan nested blocks
|
||||
if block.content then
|
||||
scan_blocks(block.content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Scan the document
|
||||
scan_blocks(doc.blocks)
|
||||
|
||||
-- Report findings
|
||||
if next(found_keys) then
|
||||
log_info("📋 Found keys in document:")
|
||||
for key, _ in pairs(found_keys) do
|
||||
if summaries[key] then
|
||||
log_info(" ✅ '" .. key .. "' - valid")
|
||||
else
|
||||
log_error(" ❌ '" .. key .. "' - INVALID (location: block " .. (key_locations[key] or "unknown") .. ")")
|
||||
end
|
||||
end
|
||||
else
|
||||
log_info("📋 No part keys found in document")
|
||||
end
|
||||
|
||||
-- Report available keys for reference
|
||||
log_info("📚 Available keys in part_summaries.yml:")
|
||||
for key, _ in pairs(summaries) do
|
||||
log_info(" - '" .. key .. "'")
|
||||
end
|
||||
|
||||
-- If there are invalid keys, stop the build
|
||||
if #invalid_keys > 0 then
|
||||
log_error("❌ CRITICAL ERROR: Invalid keys found during pre-scan:")
|
||||
for _, key in ipairs(invalid_keys) do
|
||||
log_error(" - '" .. key .. "' not found in part_summaries.yml")
|
||||
end
|
||||
log_error("🛑 Build stopped - fix all invalid keys before proceeding")
|
||||
log_error("💡 Check your .qmd files for \\part{key:" .. table.concat(invalid_keys, "} or \\part{key:") .. "} commands")
|
||||
error("Part summary filter failed: invalid keys found during pre-scan. Please check your .qmd files and part_summaries.yml for consistency.")
|
||||
else
|
||||
log_success("✅ Pre-scan validation passed - all keys are valid")
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug function to help identify the source of problematic keys
|
||||
local function debug_key_source(key, el)
|
||||
log_error("🔍 DEBUG: Key '" .. key .. "' found in RawBlock")
|
||||
log_error("📍 RawBlock content: " .. (el.text or "nil"))
|
||||
log_error("📍 RawBlock format: " .. (el.format or "nil"))
|
||||
|
||||
-- Try to extract more context about where this key came from
|
||||
if el.text then
|
||||
local context = string.sub(el.text, 1, 200) -- First 200 chars for context
|
||||
log_error("📍 Context: " .. context)
|
||||
end
|
||||
end
|
||||
|
||||
-- 🏁 Main transformation function
|
||||
-- This function intercepts \part{key:xxx} commands and transforms them
|
||||
-- into appropriate LaTeX commands based on the routing logic above
|
||||
function RawBlock(el)
|
||||
if not has_part_summaries then return nil end
|
||||
if el.format ~= "latex" then return nil end
|
||||
|
||||
local key = extract_key_from_latex(el.text)
|
||||
if key then
|
||||
local normalized_key = normalize(key)
|
||||
if summaries[normalized_key] then
|
||||
local part_entry = summaries[normalized_key]
|
||||
local title = part_entry.title
|
||||
local description = part_entry.description
|
||||
local part_type = part_entry.type or "part"
|
||||
local numbered = part_entry.numbered or false
|
||||
local formatted_title = format_part_title(normalized_key, title, numbered)
|
||||
|
||||
local setpartsummary_cmd = "\\setpartsummary{" .. description .. "}"
|
||||
local part_cmd
|
||||
|
||||
-- ROUTING LOGIC: Transform based on type field from YAML
|
||||
|
||||
-- 1. BOOK DIVISIONS: Major book structure sections
|
||||
if part_type == "division" then
|
||||
part_cmd = "\\division{" .. formatted_title .. "}"
|
||||
local toc_cmd = "\\addtocontents{toc}{\\par\\addvspace{12pt}\\noindent\\hfil\\bfseries\\color{crimson}" .. formatted_title .. "\\color{black}\\hfil\\par\\addvspace{6pt}}"
|
||||
local line_cmd = "\\addtocontents{toc}{\\par\\noindent\\hfil{\\color{crimson}\\rule{0.6\\textwidth}{0.5pt}}\\hfil\\par\\addvspace{6pt}}"
|
||||
log_info("🔄 Replacing key '" .. key .. "' with division: '" .. formatted_title .. "' (with TOC entry + crimson line)")
|
||||
return {
|
||||
pandoc.RawBlock("latex", toc_cmd),
|
||||
pandoc.RawBlock("latex", line_cmd),
|
||||
pandoc.RawBlock("latex", part_cmd)
|
||||
}
|
||||
|
||||
-- 2. LAB PLATFORMS: Circuit-style neural network design
|
||||
elseif part_type == "lab" then
|
||||
part_cmd = "\\labdivision{" .. formatted_title .. "}"
|
||||
local toc_cmd = "\\addtocontents{toc}{\\par\\addvspace{12pt}\\noindent\\hfil\\bfseries\\color{crimson}" .. formatted_title .. "\\color{black}\\hfil\\par\\addvspace{6pt}}"
|
||||
log_info("🔄 Replacing key '" .. key .. "' with lab division: '" .. formatted_title .. "' (circuit style, clean TOC entry)")
|
||||
return {
|
||||
pandoc.RawBlock("latex", toc_cmd),
|
||||
pandoc.RawBlock("latex", part_cmd)
|
||||
}
|
||||
|
||||
-- 3. NUMBERED PARTS: Main content sections (type: "part")
|
||||
elseif part_type == "part" then
|
||||
-- Reset counter if we're in a new division
|
||||
reset_part_counter(part_entry.division or "mainmatter")
|
||||
|
||||
-- Get next part number and convert to Roman numeral
|
||||
local part_number = get_next_part_number()
|
||||
local roman_numeral = to_roman(part_number)
|
||||
|
||||
part_cmd = "\\numberedpart{" .. formatted_title .. "}" -- Use custom command instead
|
||||
local toc_cmd = "\\addtocontents{toc}{\\par\\addvspace{12pt}\\noindent\\hfil\\bfseries\\color{crimson}Part~" .. roman_numeral .. "~" .. formatted_title .. "\\color{black}\\hfil\\par\\addvspace{6pt}}"
|
||||
log_info("🔄 Replacing key '" .. key .. "' with numbered part: '" .. formatted_title .. "' (Part " .. roman_numeral .. ", division: " .. (part_entry.division or "mainmatter") .. ")")
|
||||
return {
|
||||
pandoc.RawBlock("latex", setpartsummary_cmd),
|
||||
pandoc.RawBlock("latex", toc_cmd),
|
||||
pandoc.RawBlock("latex", part_cmd)
|
||||
}
|
||||
end
|
||||
else
|
||||
-- Enhanced error reporting with more context
|
||||
log_error("❌ CRITICAL ERROR: UNDEFINED KEY '" .. key .. "' not found in part_summaries.yml")
|
||||
log_error("📍 Location: RawBlock processing")
|
||||
|
||||
-- Add debug information to help identify the source
|
||||
debug_key_source(key, el)
|
||||
|
||||
log_error("🔍 Available keys: frontmatter, main_content, foundations, principles, optimization, deployment, trustworthy, futures, labs, arduino, xiao, grove, raspberry, shared, backmatter")
|
||||
log_error("💡 Check your .qmd files for \\part{key:" .. key .. "} commands")
|
||||
log_error("🛑 Build stopped to prevent incorrect part titles.")
|
||||
|
||||
-- Force immediate exit with detailed error
|
||||
local error_msg = string.format(
|
||||
"Part summary filter failed: undefined key '%s' in \\part{key:%s}. " ..
|
||||
"Available keys: frontmatter, main_content, foundations, principles, optimization, deployment, trustworthy, futures, labs, arduino, xiao, grove, raspberry, shared, backmatter. " ..
|
||||
"Please check your .qmd files and part_summaries.yml for consistency.",
|
||||
key, key
|
||||
)
|
||||
error(error_msg)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Initialize the filter with Meta handler
|
||||
function Meta(meta)
|
||||
if quarto.doc.is_format("pdf") then
|
||||
local filter_metadata = meta["filter-metadata"]
|
||||
if filter_metadata and filter_metadata["part-summaries"] then
|
||||
local config = filter_metadata["part-summaries"]
|
||||
local file_path = pandoc.utils.stringify(config.file or "")
|
||||
local enabled = pandoc.utils.stringify(config.enabled or "true"):lower() == "true"
|
||||
|
||||
if enabled and file_path ~= "" then
|
||||
log_info("🚀 Initializing Part Summary Filter")
|
||||
log_info("📂 Loading part summaries from: " .. file_path)
|
||||
|
||||
-- Add error handling for file loading
|
||||
local success, result = pcall(read_summaries, file_path)
|
||||
if success then
|
||||
summaries = result
|
||||
-- Validate that summaries were loaded properly
|
||||
if type(summaries) == "table" and next(summaries) then
|
||||
has_part_summaries = true
|
||||
log_success("Part Summary Filter activated for PDF format")
|
||||
else
|
||||
log_error("❌ CRITICAL ERROR: part_summaries.yml is empty or invalid")
|
||||
log_error("📍 File path: " .. file_path)
|
||||
log_error("🛑 Build stopped - part_summaries.yml must contain valid entries")
|
||||
error("Part summary filter failed: part_summaries.yml is empty or contains no valid entries")
|
||||
end
|
||||
else
|
||||
log_error("❌ CRITICAL ERROR: Failed to load part_summaries.yml")
|
||||
log_error("📍 File path: " .. file_path)
|
||||
log_error("🔍 Error: " .. tostring(result))
|
||||
log_error("🛑 Build stopped - cannot proceed without part summaries")
|
||||
error("Part summary filter failed: cannot load part_summaries.yml from " .. file_path .. ". Error: " .. tostring(result))
|
||||
end
|
||||
else
|
||||
log_warning("Part Summary Filter disabled or no file specified")
|
||||
end
|
||||
else
|
||||
log_warning("Part Summary Filter metadata not found")
|
||||
end
|
||||
else
|
||||
log_info("Part Summary Filter skipped (not PDF format)")
|
||||
end
|
||||
return meta
|
||||
end
|
||||
|
||||
-- Return the filter in the correct order
|
||||
return {
|
||||
{ Meta = Meta },
|
||||
{ Pandoc = function(doc)
|
||||
-- Run pre-scan validation if part summaries are enabled
|
||||
if has_part_summaries then
|
||||
prescan_document_keys(doc)
|
||||
end
|
||||
return doc
|
||||
end },
|
||||
{ RawBlock = RawBlock }
|
||||
}
|
||||
1
kits/tex/after-body-includes.tex
Normal file
1
kits/tex/after-body-includes.tex
Normal file
@@ -0,0 +1 @@
|
||||
\clearpage
|
||||
16
kits/tex/before-body-includes.tex
Normal file
16
kits/tex/before-body-includes.tex
Normal file
@@ -0,0 +1,16 @@
|
||||
% Disable the default title page
|
||||
\renewcommand{\maketitle}{
|
||||
\newgeometry{top=0.5in,bottom=1in,inner=1in,outer=1in} % Geometry for title page
|
||||
\begin{titlepage}
|
||||
\begin{center}
|
||||
\thispagestyle{empty}
|
||||
\includegraphics[trim=0 -10 0 0, clip, width=\textwidth]{cover-image-white.png} % Adjust the size and path to your image
|
||||
{{\Huge\bfseries Machine Learning Systems}\\[1em] \par}
|
||||
\vspace*{\fill}
|
||||
{\large Written, edited and curated by \\[.1cm] Prof. Vijay Janapa Reddi \\[.2cm] Harvard University \\[1em] \normalsize {\itshape With special thanks to the community for their contributions and support.} \\[1em] \vfill \scriptsize Last Modified: \today\par \vfill}
|
||||
\end{center}
|
||||
\end{titlepage}
|
||||
\restoregeometry % Restore original geometry
|
||||
}
|
||||
|
||||
\let\endtitlepage\relax
|
||||
11
kits/tex/copyright.tex
Normal file
11
kits/tex/copyright.tex
Normal file
@@ -0,0 +1,11 @@
|
||||
\null\vfill
|
||||
\begin{flushleft}
|
||||
\thispagestyle{empty}
|
||||
\textit{Here's the name of the book for the copyright page}
|
||||
|
||||
© Anyname, Inc.
|
||||
|
||||
ISBN-1234567891234
|
||||
|
||||
\noindent All rights reserved. No part of this publication may be produced or transmitted in any form or by any means, electronic or mechanical, including photocopying recording or any information storage and retrieval system, without the prior written permission of the publisher. For permissions contact
|
||||
\end{flushleft}
|
||||
9
kits/tex/cover_page.tex
Normal file
9
kits/tex/cover_page.tex
Normal file
@@ -0,0 +1,9 @@
|
||||
\begin{titlepage}
|
||||
\begin{center}
|
||||
\thispagestyle{empty}
|
||||
\includegraphics[trim=0 -10 0 0, clip, width=\textwidth]{cover-image-white.png} % Adjust the size and path to your image
|
||||
{{\Huge\bfseries Hardware Kits}\\[0.5em] \Large Hands-On Embedded ML Labs \\[1em] \par}
|
||||
\vspace*{\fill}
|
||||
{\large Written, edited and curated by \\[.1cm] Marcelo Rovai \\[.2cm] With Prof. Vijay Janapa Reddi \\[.2cm] Harvard University \\[1em] \normalsize {\itshape Part of the Machine Learning Systems textbook.} \\[1em] \vfill \scriptsize Last Modified: \today\par \vfill}
|
||||
\end{center}
|
||||
\end{titlepage}
|
||||
861
kits/tex/header-includes.tex
Normal file
861
kits/tex/header-includes.tex
Normal file
@@ -0,0 +1,861 @@
|
||||
% =============================================================================
|
||||
% LATEX HEADER CONFIGURATION FOR HARDWARE KITS PDF
|
||||
% =============================================================================
|
||||
% This file contains all LaTeX package imports, custom commands, and styling
|
||||
% definitions for the PDF output of the Hardware Kits labs.
|
||||
%
|
||||
% Key Features:
|
||||
% - Teal branding throughout (matching kits website)
|
||||
% - Custom part/chapter/section styling
|
||||
% - Professional table formatting with colored headers
|
||||
% - Margin notes with custom styling
|
||||
% - TikZ-based part dividers
|
||||
% - Page numbering (Roman for frontmatter, Arabic for mainmatter)
|
||||
%
|
||||
% Note: This file is included via _quarto-pdf.yml and affects PDF output only.
|
||||
% HTML/EPUB styling is handled separately via CSS files.
|
||||
% =============================================================================
|
||||
|
||||
% =============================================================================
|
||||
% PACKAGE IMPORTS
|
||||
% =============================================================================
|
||||
|
||||
% Layout and positioning
|
||||
% \usepackage[outercaption, ragged]{sidecap} % Commented out to make figure captions inline instead of in margin
|
||||
\usepackage{adjustbox} % Adjusting box dimensions
|
||||
\usepackage{afterpage} % Execute commands after page break
|
||||
\usepackage{morefloats} % Increase number of floats
|
||||
\usepackage{array} % Enhanced table column formatting
|
||||
\usepackage{atbegshi} % Insert content at page beginning
|
||||
%\usepackage{changepage} % Change page dimensions mid-document
|
||||
\usepackage{emptypage} % Clear headers/footers on empty pages
|
||||
|
||||
% Language and text
|
||||
\usepackage[english]{babel} % English language support
|
||||
\usepackage{microtype} % Improved typography and hyphenation
|
||||
|
||||
% Captions and floats
|
||||
\usepackage{caption}
|
||||
% Caption styling configuration
|
||||
%\captionsetup[table]{belowskip=5pt}
|
||||
\captionsetup{format=plain}
|
||||
\DeclareCaptionLabelFormat{mylabel}{#1
|
||||
#2:\hspace{1.0ex}}
|
||||
\DeclareCaptionFont{ninept}{\fontsize{7pt}{8}\selectfont #1}
|
||||
|
||||
% Figure captions: Small font, bold label, ragged right
|
||||
\captionsetup[figure]{labelfont={bf,ninept},labelsep=space,
|
||||
belowskip=2pt,aboveskip=6pt,labelformat=mylabel,
|
||||
justification=raggedright,singlelinecheck=false,font={ninept}}
|
||||
|
||||
% Table captions: Small font, bold label, ragged right
|
||||
\captionsetup[table]{belowskip=6pt,labelfont={bf,ninept},labelsep=none,
|
||||
labelformat=mylabel,justification=raggedright,singlelinecheck=false,font={ninept}}
|
||||
|
||||
% Typography fine-tuning
|
||||
\emergencystretch=5pt % Allow extra stretch to avoid overfull boxes
|
||||
|
||||
% Utility packages
|
||||
\usepackage{etoolbox} % For patching commands and environments
|
||||
|
||||
% Page layout and headers
|
||||
\usepackage{fancyhdr} % Custom headers and footers
|
||||
\usepackage{geometry} % Page dimensions and margins
|
||||
|
||||
% Graphics and figures
|
||||
\usepackage{graphicx} % Include graphics
|
||||
\usepackage{float} % Improved float placement
|
||||
\usepackage[skins,breakable]{tcolorbox} % Coloured and framed text boxes
|
||||
\tcbset{before upper=\setlength{\parskip}{3pt}}
|
||||
|
||||
% Tables
|
||||
\usepackage{longtable} % Multi-page tables
|
||||
|
||||
% Fonts and typography
|
||||
\usepackage{fontspec} % Font selection for LuaLaTeX
|
||||
\usepackage{mathptmx} % Times-like math fonts
|
||||
\usepackage{newpxtext} % Palatino-like font for body text
|
||||
|
||||
% Colors and visual elements
|
||||
\usepackage[dvipsnames]{xcolor} % Extended color support
|
||||
\usepackage{tikz} % Programmatic graphics
|
||||
\usetikzlibrary{positioning}
|
||||
\usetikzlibrary{calc}
|
||||
\usepackage{tikzpagenodes} % TikZ positioning relative to page
|
||||
|
||||
% Code listings
|
||||
\usepackage{listings} % Code highlighting
|
||||
|
||||
% Hyperlinks
|
||||
\usepackage{hyperref} % Clickable links in PDF
|
||||
|
||||
% Conditional logic
|
||||
\usepackage{ifthen} % If-then-else commands
|
||||
|
||||
% Math symbols
|
||||
\usepackage{amsmath} % AMS math extensions
|
||||
\usepackage{amssymb} % AMS math symbols
|
||||
\usepackage{latexsym} % Additional LaTeX symbols
|
||||
\usepackage{pifont} % Zapf Dingbats symbols
|
||||
\providecommand{\blacklozenge}{\ding{117}} % Black diamond symbol
|
||||
|
||||
% Lists
|
||||
\usepackage{enumitem} % Customizable lists
|
||||
|
||||
% Margin notes and sidenotes
|
||||
\usepackage{marginfix} % Fixes margin note overflow
|
||||
\usepackage{marginnote} % Margin notes
|
||||
\usepackage{sidenotes} % Academic-style sidenotes
|
||||
\renewcommand\raggedrightmarginnote{\sloppy}
|
||||
\renewcommand\raggedleftmarginnote{\sloppy}
|
||||
|
||||
% Typography improvements
|
||||
\usepackage{ragged2e} % Better ragged text
|
||||
\usepackage[all]{nowidow} % Prevent widows and orphans
|
||||
\usepackage{needspace} % Ensure minimum space on page
|
||||
|
||||
% Section formatting
|
||||
\usepackage[explicit]{titlesec} % Custom section titles
|
||||
\usepackage{tocloft} % Table of contents formatting
|
||||
|
||||
% QR codes and icons
|
||||
\usepackage{fontawesome5} % Font Awesome icons
|
||||
\usepackage{qrcode} % QR code generation
|
||||
\qrset{link, height=15mm}
|
||||
|
||||
% =============================================================================
|
||||
% FLOAT CONFIGURATION
|
||||
% =============================================================================
|
||||
% Allow more floats per page to handle figure-heavy chapters
|
||||
\extrafloats{100}
|
||||
\setcounter{topnumber}{10} % Max floats at top of page
|
||||
\setcounter{bottomnumber}{10} % Max floats at bottom of page
|
||||
\setcounter{totalnumber}{20} % Max floats per page
|
||||
\renewcommand{\topfraction}{.9} % Max fraction of page for top floats
|
||||
\renewcommand{\bottomfraction}{.9}
|
||||
\renewcommand{\textfraction}{.1} % Min fraction of page for text
|
||||
\renewcommand{\floatpagefraction}{.8} % Min fraction of float page
|
||||
|
||||
% =============================================================================
|
||||
% COLOR DEFINITIONS
|
||||
% =============================================================================
|
||||
% Teal - primary brand color for Hardware Kits (matching website)
|
||||
\definecolor{crimson}{HTML}{0d9488}
|
||||
|
||||
% Quiz element colors
|
||||
\definecolor{quiz-question-color1}{RGB}{225,243,248} % Light blue background
|
||||
\definecolor{quiz-question-color2}{RGB}{17,158,199} % Blue border
|
||||
\definecolor{quiz-answer-color1}{RGB}{250,234,241} % Light pink background
|
||||
\definecolor{quiz-answer-color2}{RGB}{152,14,90} % Magenta border
|
||||
|
||||
% =============================================================================
|
||||
% LIST FORMATTING
|
||||
% =============================================================================
|
||||
% Tighter list spacing for academic style
|
||||
\def\tightlist{}
|
||||
\setlist{itemsep=1pt, parsep=1pt, topsep=0pt,after={\vspace{0.3\baselineskip}}}
|
||||
\let\tightlist\relax
|
||||
|
||||
\makeatletter
|
||||
\@ifpackageloaded{framed}{}{\usepackage{framed}}
|
||||
\@ifpackageloaded{fancyvrb}{}{\usepackage{fancyvrb}}
|
||||
\makeatother
|
||||
|
||||
\makeatletter
|
||||
%New float "codelisting" has been updated
|
||||
\AtBeginDocument{%
|
||||
\floatstyle{ruled}
|
||||
\newfloat{codelisting}{!htb}{lop}
|
||||
\floatname{codelisting}{Listing}
|
||||
\floatplacement{codelisting}{!htb}
|
||||
\captionsetup[codelisting]{labelfont={bf,ninept},labelformat=mylabel,
|
||||
singlelinecheck=false,width=\linewidth,labelsep=none,font={ninept}}%
|
||||
\renewenvironment{snugshade}{%
|
||||
\def\OuterFrameSep{3pt}%
|
||||
\def\FrameCommand{\fboxsep=5pt\colorbox{shadecolor}}%
|
||||
\MakeFramed{\advance\hsize-\width\FrameRestore}%
|
||||
\leftskip 0.5em \rightskip 0.5em%
|
||||
\small% decrease font size
|
||||
}{\endMakeFramed}%
|
||||
}
|
||||
\makeatother
|
||||
|
||||
%The space before and after the verbatim environment "Highlighting" has been reduced
|
||||
\fvset{listparameters=\setlength{\topsep}{0pt}\setlength{\partopsep}{0pt}}
|
||||
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{framesep=0mm,commandchars=\\\{\}}
|
||||
|
||||
\makeatletter
|
||||
\renewcommand\fs@ruled{\def\@fs@cfont{\bfseries}\let\@fs@capt\floatc@ruled
|
||||
\def\@fs@pre{\hrule height.8pt depth0pt \kern2pt}%
|
||||
\def\@fs@post{\kern2pt\hrule\relax}%
|
||||
\def\@fs@mid{\kern2pt\hrule\kern1pt}%space between float and caption
|
||||
\let\@fs@iftopcapt\iftrue}
|
||||
\makeatother
|
||||
|
||||
|
||||
% =============================================================================
|
||||
% HYPHENATION RULES
|
||||
% =============================================================================
|
||||
% Explicit hyphenation points for technical terms to avoid bad breaks
|
||||
\hyphenation{
|
||||
light-weight
|
||||
light-weight-ed
|
||||
de-vel-op-ment
|
||||
un-der-stand-ing
|
||||
mod-els
|
||||
prin-ci-ples
|
||||
ex-per-tise
|
||||
com-pli-cat-ed
|
||||
blue-print
|
||||
per‧for‧mance
|
||||
com-mu-ni-ca-tion
|
||||
par-a-digms
|
||||
hy-per-ten-sion
|
||||
a-chieved
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% CODE LISTING CONFIGURATION
|
||||
% =============================================================================
|
||||
% Settings for code blocks using listings package
|
||||
\lstset{
|
||||
breaklines=true, % Automatic line wrapping
|
||||
breakatwhitespace=true, % Break at whitespace only
|
||||
basicstyle=\ttfamily, % Monospace font
|
||||
frame=none, % No frame around code
|
||||
keepspaces=true, % Preserve spaces
|
||||
showspaces=false, % Don't show space characters
|
||||
showtabs=false, % Don't show tab characters
|
||||
columns=flexible, % Flexible column width
|
||||
belowskip=0pt, % Minimal spacing
|
||||
aboveskip=0pt
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% PAGE GEOMETRY
|
||||
% =============================================================================
|
||||
% Book dimensions optimized for print (7.5" × 9.25")
|
||||
% Wide outer margin for sidenotes (1.5")
|
||||
\geometry{
|
||||
paperwidth=7.5in,
|
||||
paperheight=9.25in,
|
||||
top=1in,
|
||||
bottom=1in,
|
||||
inner=1in, % Inner margin (binding side)
|
||||
outer=2.25in, % Outer margin (includes 1.5" for sidenotes)
|
||||
footskip=30pt,
|
||||
marginparwidth=1.5in, % Width for margin notes
|
||||
twoside % Different left/right pages
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% SIDENOTE STYLING
|
||||
% =============================================================================
|
||||
% Custom sidenote design with crimson vertical bar
|
||||
\renewcommand{\thefootnote}{\textcolor{crimson}{\arabic{footnote}}}
|
||||
|
||||
% Save original sidenote command
|
||||
\makeatletter
|
||||
\@ifundefined{oldsidenote}{
|
||||
\let\oldsidenote\sidenote%
|
||||
}{}
|
||||
\makeatother
|
||||
|
||||
% Redefine sidenote with vertical crimson bar
|
||||
\renewcommand{\sidenote}[1]{%
|
||||
\oldsidenote{%
|
||||
\noindent
|
||||
\color{crimson!100} % Crimson vertical line
|
||||
\raisebox{0em}{%
|
||||
\rule{0.5pt}{1.5em} % Thin vertical line
|
||||
}
|
||||
\hspace{0.3em} % Space after line
|
||||
\color{black} % Reset text color
|
||||
\footnotesize #1 % Sidenote content
|
||||
}%
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% FIGURE ENVIRONMENT FIX
|
||||
% =============================================================================
|
||||
% Fix for caption display bug on even pages
|
||||
\makeatletter
|
||||
\let\oldfigure\figure%
|
||||
\let\endoldfigure\endfigure%
|
||||
\renewenvironment{figure}[1][htbp]{%
|
||||
\oldfigure[#1]%
|
||||
}{%
|
||||
\endoldfigure%
|
||||
}
|
||||
\makeatother
|
||||
|
||||
% =============================================================================
|
||||
% PAGE HEADERS AND FOOTERS
|
||||
% =============================================================================
|
||||
% Ensure chapters use fancy page style (not plain)
|
||||
\patchcmd{\chapter}{\thispagestyle{plain}}{\thispagestyle{fancy}}{}{}
|
||||
|
||||
% Main page style with crimson headers
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{} % Clear all
|
||||
\fancyhead[LE]{\small\color{crimson}\nouppercase{\rightmark}} % Left even: section
|
||||
\fancyhead[RO]{\color{crimson}\thepage} % Right odd: page number
|
||||
\fancyhead[LO]{\small\color{crimson}\nouppercase{\leftmark}} % Left odd: chapter
|
||||
\fancyhead[RE]{\color{crimson}\thepage} % Right even: page number
|
||||
\renewcommand{\headrulewidth}{0.4pt} % Thin header line
|
||||
\renewcommand{\footrulewidth}{0pt} % No footer line
|
||||
|
||||
% Plain page style (for chapter openings)
|
||||
\fancypagestyle{plain}{
|
||||
\fancyhf{}
|
||||
\fancyfoot[C]{\color{crimson}\thepage} % Centered page number
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\renewcommand{\footrulewidth}{0pt}
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% KOMA-SCRIPT FONT ADJUSTMENTS
|
||||
% =============================================================================
|
||||
% Apply crimson color to all heading levels
|
||||
\addtokomafont{disposition}{\rmfamily\color{crimson}}
|
||||
\addtokomafont{chapter}{\color{crimson}}
|
||||
\addtokomafont{section}{\color{crimson}}
|
||||
\addtokomafont{subsection}{\color{crimson}}
|
||||
|
||||
% =============================================================================
|
||||
% ABSTRACT ENVIRONMENT
|
||||
% =============================================================================
|
||||
\newenvironment{abstract}{
|
||||
\chapter*{\abstractname}
|
||||
\addcontentsline{toc}{chapter}{\abstractname}
|
||||
\small
|
||||
}{
|
||||
\clearpage
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% HYPERLINK CONFIGURATION
|
||||
% =============================================================================
|
||||
% Crimson-colored links throughout, two-page PDF layout
|
||||
\hypersetup{
|
||||
linkcolor=crimson,
|
||||
citecolor=crimson,
|
||||
urlcolor=crimson,
|
||||
pdfpagelayout=TwoPageRight, % Two-page spread view
|
||||
pdfstartview=Fit % Initial zoom fits page
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% PART SUMMARY SYSTEM
|
||||
% =============================================================================
|
||||
% Allows adding descriptive text below part titles
|
||||
\newcommand{\partsummary}{} % Empty by default
|
||||
\newif\ifhaspartsummary%
|
||||
\haspartsummaryfalse%
|
||||
|
||||
\newcommand{\setpartsummary}[1]{%
|
||||
\renewcommand{\partsummary}{#1}%
|
||||
\haspartsummarytrue%
|
||||
}
|
||||
|
||||
% Additional colors for part page backgrounds
|
||||
\definecolor{BrownLL}{RGB}{233,222,220}
|
||||
\definecolor{BlueDD}{RGB}{62,100,125}
|
||||
\colorlet{BlueDD}{magenta}
|
||||
|
||||
% ===============================================================================
|
||||
% PART STYLING SYSTEM
|
||||
% ===============================================================================
|
||||
%
|
||||
% This system provides three distinct visual styles for book organization:
|
||||
%
|
||||
% 1. NUMBERED PARTS (\part{title}) - For main book sections
|
||||
% - Roman numerals (I, II, III, etc.) in top right corner
|
||||
% - Crimson title with horizontal lines above/below
|
||||
% - "Part I" label in sidebar
|
||||
% - Used for: foundations, principles, optimization, deployment, etc.
|
||||
%
|
||||
% 2. UNNUMBERED PARTS (\part*{title}) - For special sections like "Labs"
|
||||
% - Division-style geometric background (left side)
|
||||
% - No Roman numerals
|
||||
% - Used for: labs section
|
||||
%
|
||||
% 3. DIVISIONS (\division{title}) - For major book divisions
|
||||
% - Clean geometric background with centered title
|
||||
% - Used for: frontmatter, main_content, backmatter
|
||||
%
|
||||
% The Lua filter (inject-parts.lua) automatically routes parts by {key:xxx} commands
|
||||
% to the appropriate LaTeX command based on the key name.
|
||||
% ===============================================================================
|
||||
|
||||
% NUMBERED PARTS: Roman numeral styling for main book sections
|
||||
\titleformat{\part}[display]
|
||||
{\thispagestyle{empty}}{}{20pt}{
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
%%%
|
||||
%%
|
||||
\node[crimson,align=flush right,
|
||||
inner sep=0,outer sep=0mm,draw=none,%
|
||||
anchor=east,minimum height=31mm, text width=1.2\textwidth,
|
||||
yshift=-30mm,font={%
|
||||
\fontsize{98pt}{104}\selectfont\bfseries}] (BG) at (current page text area.north east){\thepart};
|
||||
%
|
||||
\node[black,inner sep=0mm,draw=none,
|
||||
anchor=mid,text width=1.2\textwidth,
|
||||
minimum height=35mm, align=right,
|
||||
node distance=7mm,below=of BG,
|
||||
font={\fontsize{30pt}{34}\selectfont}]
|
||||
(BGG) {\hyphenchar\font=-1 \color{black}\MakeUppercase {#1}};
|
||||
\draw [crimson,line width=3pt] ([yshift=0mm]BGG.north west) -- ([yshift=0mm]BGG.north east);
|
||||
\draw [crimson,line width=2pt] ([yshift=0mm]BGG.south west) -- ([yshift=0mm]BGG.south east);
|
||||
%
|
||||
\node[fill=crimson,text=white,rotate=90,%
|
||||
anchor=south west,minimum height=15mm,
|
||||
minimum width=40mm,font={%
|
||||
\fontsize{20pt}{20}\selectfont\bfseries}](BP) at
|
||||
(current page text area.south east)
|
||||
{{\sffamily Part}~\thepart};
|
||||
%
|
||||
\path[red](BP.north west)-|coordinate(PS)(BGG.south west);
|
||||
%
|
||||
\ifhaspartsummary
|
||||
\node[inner sep=4pt,text width=0.7\textwidth,draw=none,fill=BrownLL!40,
|
||||
align=justify,font={\fontsize{9pt}{12}\selectfont},anchor=south west]
|
||||
at (PS) {\partsummary};
|
||||
\fi
|
||||
\end{tikzpicture}
|
||||
}[]
|
||||
|
||||
\renewcommand{\thepart}{\Roman{part}}
|
||||
|
||||
% UNNUMBERED PARTS: Division-style background for special sections
|
||||
\titleformat{name=\part,numberless}[display]
|
||||
{\thispagestyle{empty}}{}{20pt}{
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
%%%
|
||||
\coordinate(S1)at([yshift=-200mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!7](S1)--++(45:16)coordinate(S2)-
|
||||
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
|
||||
%
|
||||
\coordinate(E1)at([yshift=-98mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!15](E1)--(current page.north west)coordinate(E2)
|
||||
--++(0:98mm)coordinate(E3)--(E1);
|
||||
%
|
||||
\coordinate(D1)at([yshift=15mm]current page.south west);
|
||||
\draw[draw=none,fill=BlueDD!40,opacity=0.5](D1)--++(45:5.5)coordinate(D2)
|
||||
-|(D2|-current page.north west)--(current page.north west)coordinate(D3)--(D1);
|
||||
%%%%
|
||||
\path[red](S2)-|(S2-|current page.east)coordinate(SS2);
|
||||
%PART
|
||||
\node[crimson,align=flush right,inner sep=0,outer sep=0mm,draw=none,anchor=south,
|
||||
font={\fontsize{48pt}{48}\selectfont\bfseries}] (BG) at ($(S2)!0.5!(SS2)$){\hphantom{Part}};
|
||||
%%%
|
||||
\path[green]([yshift=15mm]D2)-|coordinate(TPD)(BG.south east);
|
||||
\node[inner sep=0mm,draw=none,anchor=south east,%text width=0.9\textwidth,
|
||||
align=right,font={\fontsize{40pt}{40}\selectfont}]
|
||||
(BGG) at (TPD) {\color{crimson}\MakeUppercase {#1}};%\MakeUppercase {}
|
||||
\end{tikzpicture}
|
||||
}
|
||||
|
||||
% Define \numberedpart command for numbered parts
|
||||
\newcommand{\numberedpart}[1]{%
|
||||
\clearpage
|
||||
\thispagestyle{empty}
|
||||
\stepcounter{part}%
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
%%%
|
||||
%%
|
||||
\node[crimson,align=flush right,
|
||||
inner sep=0,outer sep=0mm,draw=none,%
|
||||
anchor=east,minimum height=31mm, text width=1.2\textwidth,
|
||||
yshift=-30mm,font={%
|
||||
\fontsize{98pt}{104}\selectfont\bfseries}] (BG) at (current page text area.north east){\thepart};
|
||||
%
|
||||
\node[black,inner sep=0mm,draw=none,
|
||||
anchor=mid,text width=1.2\textwidth,
|
||||
minimum height=35mm, align=right,
|
||||
node distance=7mm,below=of BG,
|
||||
font={\fontsize{30pt}{34}\selectfont}]
|
||||
(BGG) {\hyphenchar\font=-1 \color{black}\MakeUppercase {#1}};
|
||||
\draw [crimson,line width=3pt] ([yshift=0mm]BGG.north west) -- ([yshift=0mm]BGG.north east);
|
||||
\draw [crimson,line width=2pt] ([yshift=0mm]BGG.south west) -- ([yshift=0mm]BGG.south east);
|
||||
%
|
||||
\node[fill=crimson,text=white,rotate=90,%
|
||||
anchor=south west,minimum height=15mm,
|
||||
minimum width=40mm,font={%
|
||||
\fontsize{20pt}{20}\selectfont\bfseries}](BP) at
|
||||
(current page text area.south east)
|
||||
{{\sffamily Part}~\thepart};
|
||||
%
|
||||
\path[red](BP.north west)-|coordinate(PS)(BGG.south west);
|
||||
%
|
||||
\ifhaspartsummary
|
||||
\node[inner sep=4pt,text width=0.7\textwidth,draw=none,fill=BrownLL!40,
|
||||
align=justify,font={\fontsize{9pt}{12}\selectfont},anchor=south west]
|
||||
at (PS) {\partsummary};
|
||||
\fi
|
||||
\end{tikzpicture}
|
||||
\clearpage
|
||||
}
|
||||
|
||||
|
||||
|
||||
% DIVISIONS: Clean geometric styling with subtle tech elements
|
||||
% Used for frontmatter, main_content, and backmatter divisions
|
||||
\newcommand{\division}[1]{%
|
||||
\clearpage
|
||||
\thispagestyle{empty}
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
|
||||
% Clean geometric background (original design)
|
||||
\coordinate(S1)at([yshift=-200mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!7](S1)--++(45:16)coordinate(S2)-
|
||||
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
|
||||
|
||||
\coordinate(E1)at([yshift=-98mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!15](E1)--(current page.north west)coordinate(E2)
|
||||
--++(0:98mm)coordinate(E3)--(E1);
|
||||
|
||||
\coordinate(D1)at([yshift=15mm]current page.south west);
|
||||
\draw[draw=none,fill=BlueDD!40,opacity=0.5](D1)--++(45:5.5)coordinate(D2)
|
||||
-|(D2|-current page.north west)--(current page.north west)coordinate(D3)--(D1);
|
||||
|
||||
% Subtle tech elements - positioned in white areas for better visibility
|
||||
% Upper right white area - more visible
|
||||
\draw[crimson!40, line width=0.8pt] ([xshift=140mm,yshift=-60mm]current page.north west) -- ++(40mm,0);
|
||||
\draw[crimson!40, line width=0.8pt] ([xshift=150mm,yshift=-70mm]current page.north west) -- ++(30mm,0);
|
||||
\draw[crimson!35, line width=0.7pt] ([xshift=160mm,yshift=-60mm]current page.north west) -- ++(0,-15mm);
|
||||
\draw[crimson!35, line width=0.7pt] ([xshift=170mm,yshift=-70mm]current page.north west) -- ++(0,10mm);
|
||||
|
||||
% Circuit nodes - upper right
|
||||
\fill[crimson!50] ([xshift=160mm,yshift=-60mm]current page.north west) circle (1.5mm);
|
||||
\fill[white] ([xshift=160mm,yshift=-60mm]current page.north west) circle (0.8mm);
|
||||
\fill[crimson!50] ([xshift=170mm,yshift=-70mm]current page.north west) circle (1.3mm);
|
||||
\fill[white] ([xshift=170mm,yshift=-70mm]current page.north west) circle (0.6mm);
|
||||
|
||||
% Lower right white area - enhanced visibility
|
||||
\draw[crimson!45, line width=0.9pt] ([xshift=140mm,yshift=-190mm]current page.north west) -- ++(45mm,0);
|
||||
\draw[crimson!45, line width=0.9pt] ([xshift=150mm,yshift=-200mm]current page.north west) -- ++(35mm,0);
|
||||
\draw[crimson!40, line width=0.8pt] ([xshift=160mm,yshift=-190mm]current page.north west) -- ++(0,-20mm);
|
||||
\draw[crimson!40, line width=0.8pt] ([xshift=170mm,yshift=-200mm]current page.north west) -- ++(0,15mm);
|
||||
|
||||
% Additional connecting lines in lower right
|
||||
\draw[crimson!35, line width=0.7pt] ([xshift=130mm,yshift=-180mm]current page.north west) -- ++(25mm,0);
|
||||
\draw[crimson!35, line width=0.7pt] ([xshift=145mm,yshift=-180mm]current page.north west) -- ++(0,-25mm);
|
||||
|
||||
% Circuit nodes - lower right (more prominent)
|
||||
\fill[crimson!55] ([xshift=160mm,yshift=-190mm]current page.north west) circle (1.6mm);
|
||||
\fill[white] ([xshift=160mm,yshift=-190mm]current page.north west) circle (0.9mm);
|
||||
\fill[crimson!55] ([xshift=170mm,yshift=-200mm]current page.north west) circle (1.4mm);
|
||||
\fill[white] ([xshift=170mm,yshift=-200mm]current page.north west) circle (0.7mm);
|
||||
\fill[crimson!50] ([xshift=145mm,yshift=-180mm]current page.north west) circle (1.2mm);
|
||||
\fill[white] ([xshift=145mm,yshift=-180mm]current page.north west) circle (0.6mm);
|
||||
|
||||
% Title positioned in center - clean and readable
|
||||
\node[inner sep=0mm,draw=none,anchor=center,text width=0.8\textwidth,
|
||||
align=center,font={\fontsize{40pt}{40}\selectfont}]
|
||||
(BGG) at (current page.center) {\color{crimson}\MakeUppercase {#1}};
|
||||
|
||||
\end{tikzpicture}
|
||||
\clearpage
|
||||
}
|
||||
|
||||
% LAB DIVISIONS: Circuit-style neural network design for lab sections
|
||||
% Used specifically for lab platform sections (arduino, xiao, grove, etc.)
|
||||
\newcommand{\labdivision}[1]{%
|
||||
\clearpage
|
||||
\thispagestyle{empty}
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
% Circuit background with subtle gradient
|
||||
\coordinate(S1)at([yshift=-200mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!5](S1)--++(45:16)coordinate(S2)-
|
||||
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
|
||||
|
||||
% TOP AREA: Circuit lines in upper white space
|
||||
\draw[crimson!50, line width=1.5pt] ([xshift=30mm,yshift=-40mm]current page.north west) -- ++(60mm,0);
|
||||
\draw[crimson!40, line width=1pt] ([xshift=120mm,yshift=-50mm]current page.north west) -- ++(50mm,0);
|
||||
\draw[crimson!50, line width=1.5pt] ([xshift=40mm,yshift=-70mm]current page.north west) -- ++(40mm,0);
|
||||
|
||||
% Connecting lines in top area
|
||||
\draw[crimson!30, line width=1pt] ([xshift=60mm,yshift=-40mm]current page.north west) -- ++(0,-20mm);
|
||||
\draw[crimson!30, line width=1pt] ([xshift=145mm,yshift=-50mm]current page.north west) -- ++(0,10mm);
|
||||
|
||||
% Neural nodes in top area
|
||||
\fill[crimson!70] ([xshift=60mm,yshift=-40mm]current page.north west) circle (2.5mm);
|
||||
\fill[white] ([xshift=60mm,yshift=-40mm]current page.north west) circle (1.5mm);
|
||||
\fill[crimson!60] ([xshift=145mm,yshift=-50mm]current page.north west) circle (2mm);
|
||||
\fill[white] ([xshift=145mm,yshift=-50mm]current page.north west) circle (1mm);
|
||||
\fill[crimson!80] ([xshift=80mm,yshift=-70mm]current page.north west) circle (2mm);
|
||||
\fill[white] ([xshift=80mm,yshift=-70mm]current page.north west) circle (1mm);
|
||||
|
||||
% BOTTOM AREA: Circuit lines in lower white space
|
||||
\draw[crimson!50, line width=1.5pt] ([xshift=20mm,yshift=-200mm]current page.north west) -- ++(70mm,0);
|
||||
\draw[crimson!40, line width=1pt] ([xshift=110mm,yshift=-210mm]current page.north west) -- ++(60mm,0);
|
||||
\draw[crimson!50, line width=1.5pt] ([xshift=35mm,yshift=-230mm]current page.north west) -- ++(45mm,0);
|
||||
|
||||
% Connecting lines in bottom area
|
||||
\draw[crimson!30, line width=1pt] ([xshift=55mm,yshift=-200mm]current page.north west) -- ++(0,-20mm);
|
||||
\draw[crimson!30, line width=1pt] ([xshift=140mm,yshift=-210mm]current page.north west) -- ++(0,15mm);
|
||||
|
||||
% Neural nodes in bottom area
|
||||
\fill[crimson!70] ([xshift=55mm,yshift=-200mm]current page.north west) circle (2.5mm);
|
||||
\fill[white] ([xshift=55mm,yshift=-200mm]current page.north west) circle (1.5mm);
|
||||
\fill[crimson!60] ([xshift=140mm,yshift=-210mm]current page.north west) circle (2mm);
|
||||
\fill[white] ([xshift=140mm,yshift=-210mm]current page.north west) circle (1mm);
|
||||
\fill[crimson!80] ([xshift=80mm,yshift=-230mm]current page.north west) circle (2mm);
|
||||
\fill[white] ([xshift=80mm,yshift=-230mm]current page.north west) circle (1mm);
|
||||
|
||||
% SIDE AREAS: Subtle circuit elements on left and right edges
|
||||
\draw[crimson!30, line width=1pt] ([xshift=15mm,yshift=-120mm]current page.north west) -- ++(20mm,0);
|
||||
\draw[crimson!30, line width=1pt] ([xshift=175mm,yshift=-130mm]current page.north west) -- ++(15mm,0);
|
||||
\fill[crimson!50] ([xshift=25mm,yshift=-120mm]current page.north west) circle (1.5mm);
|
||||
\fill[white] ([xshift=25mm,yshift=-120mm]current page.north west) circle (0.8mm);
|
||||
\fill[crimson!50] ([xshift=185mm,yshift=-130mm]current page.north west) circle (1.5mm);
|
||||
\fill[white] ([xshift=185mm,yshift=-130mm]current page.north west) circle (0.8mm);
|
||||
|
||||
% Title positioned in center - CLEAN AREA
|
||||
\node[inner sep=0mm,draw=none,anchor=center,text width=0.8\textwidth,
|
||||
align=center,font={\fontsize{44pt}{44}\selectfont\bfseries}]
|
||||
(BGG) at (current page.center) {\color{crimson}\MakeUppercase {#1}};
|
||||
|
||||
\end{tikzpicture}
|
||||
\clearpage
|
||||
}
|
||||
|
||||
% Define \lab command for lab styling (different visual treatment)
|
||||
\newcommand{\lab}[1]{%
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
%%%
|
||||
% Different background pattern for labs
|
||||
\coordinate(S1)at([yshift=-200mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!15](S1)--++(45:16)coordinate(S2)-
|
||||
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
|
||||
%
|
||||
\coordinate(E1)at([yshift=-98mm]current page.north west);
|
||||
\draw[draw=none,fill=BlueDD!25](E1)--(current page.north west)coordinate(E2)
|
||||
--++(0:98mm)coordinate(E3)--(E1);
|
||||
%
|
||||
\coordinate(D1)at([yshift=15mm]current page.south west);
|
||||
\draw[draw=none,fill=BlueDD!60,opacity=0.7](D1)--++(45:5.5)coordinate(D2)
|
||||
-|(D2|-current page.north west)--(current page.north west)coordinate(D3)--(D1);
|
||||
%%%%
|
||||
\path[red](S2)-|(S2-|current page.east)coordinate(SS2);
|
||||
%LAB - Different styling
|
||||
\node[crimson,align=flush right,inner sep=0,outer sep=0mm,draw=none,anchor=south,
|
||||
font={\fontsize{48pt}{48}\selectfont\bfseries}] (BG) at ($(S2)!0.5!(SS2)$){\hphantom{Workshop}};
|
||||
%%%
|
||||
\path[green]([yshift=15mm]D2)-|coordinate(TPD)(BG.south east);
|
||||
\node[inner sep=0mm,draw=none,anchor=south east,%text width=0.9\textwidth,
|
||||
align=right,font={\fontsize{40pt}{40}\selectfont}]
|
||||
(BGG) at (TPD) {\color{crimson}\MakeUppercase {#1}};%\MakeUppercase {}
|
||||
\end{tikzpicture}
|
||||
\thispagestyle{empty}
|
||||
\clearpage
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% SECTION FORMATTING
|
||||
% =============================================================================
|
||||
% All section levels use crimson color and are ragged right
|
||||
|
||||
% Section (Large, bold, crimson)
|
||||
\titleformat{\section}
|
||||
{\normalfont\Large\bfseries\color{crimson}\raggedright}
|
||||
{\thesection}
|
||||
{0.5em}
|
||||
{#1}
|
||||
\titlespacing*{\section}{0pc}{14pt plus 4pt minus 4pt}{6pt plus 2pt minus 2pt}[0pc]
|
||||
|
||||
% Subsection (large, bold, crimson)
|
||||
\titleformat{\subsection}
|
||||
{\normalfont\large\bfseries\color{crimson}\raggedright}
|
||||
{\thesubsection}
|
||||
{0.5em}
|
||||
{#1}
|
||||
\titlespacing*{\subsection}{0pc}{12pt plus 4pt minus 4pt}{5pt plus 1pt minus 2pt}[0pc]
|
||||
|
||||
% Subsubsection (normal size, bold, crimson)
|
||||
\titleformat{\subsubsection}
|
||||
{\normalfont\normalsize\bfseries\color{crimson}\raggedright}
|
||||
{\thesubsubsection}
|
||||
{0.5em}
|
||||
{#1}
|
||||
\titlespacing*{\subsubsection}{0pc}{12pt plus 4pt minus 4pt}{5pt plus 1pt minus 2pt}[0pc]
|
||||
|
||||
% Paragraph (run-in, bold, crimson, ends with period)
|
||||
\titleformat{\paragraph}[runin]
|
||||
{\normalfont\normalsize\bfseries\color{crimson}}
|
||||
{\theparagraph}
|
||||
{0.5em}
|
||||
{#1}
|
||||
[\textbf{.}]
|
||||
\titlespacing*{\paragraph}{0pc}{6pt plus 2pt minus 2pt}{0.5em}[0pc]
|
||||
|
||||
% Subparagraph (run-in, italic, crimson, ends with period)
|
||||
\titleformat{\subparagraph}[runin]
|
||||
{\normalfont\normalsize\itshape\color{crimson}}
|
||||
{\thesubparagraph}
|
||||
{0.5em}
|
||||
{#1}
|
||||
[\textbf{.}]
|
||||
\titlespacing*{\subparagraph}{0pc}{6pt plus 2pt minus 2pt}{0.5em}[0pc]
|
||||
|
||||
% =============================================================================
|
||||
% CHAPTER FORMATTING
|
||||
% =============================================================================
|
||||
% Numbered chapters: "Chapter X" prefix, huge crimson title
|
||||
\titleformat{\chapter}[display]
|
||||
{\normalfont\huge\bfseries\color{crimson}}
|
||||
{\chaptername\ \thechapter}
|
||||
{20pt}
|
||||
{\Huge #1}
|
||||
[]
|
||||
|
||||
% Unnumbered chapters: no prefix, huge crimson title
|
||||
\titleformat{name=\chapter,numberless}
|
||||
{\normalfont\huge\bfseries\color{crimson}}
|
||||
{}
|
||||
{0pt}
|
||||
{\Huge #1}
|
||||
[]
|
||||
|
||||
\renewcommand{\chaptername}{Chapter}
|
||||
% =============================================================================
|
||||
% TABLE OF CONTENTS FORMATTING
|
||||
% =============================================================================
|
||||
\setcounter{tocdepth}{2} % Show chapters, sections, subsections
|
||||
|
||||
% TOC spacing adjustments for number widths and indentation
|
||||
\setlength{\cftchapnumwidth}{2em} % Chapter number width
|
||||
\setlength{\cftsecnumwidth}{2.75em} % Section number width
|
||||
\setlength{\cftsubsecnumwidth}{3.25em} % Subsection number width
|
||||
\setlength{\cftsubsubsecnumwidth}{4em} % Subsubsection number width
|
||||
\setlength{\cftsubsecindent}{4.25em} % Subsection indent
|
||||
\setlength{\cftsubsubsecindent}{7.5em} % Subsubsection indent
|
||||
|
||||
% Chapter entries in TOC: bold crimson with "Chapter" prefix
|
||||
\renewcommand{\cftchapfont}{\bfseries\color{crimson}}
|
||||
\renewcommand{\cftchappresnum}{\color{crimson}Chapter~}
|
||||
|
||||
% Custom formatting for division entries (styled like parts)
|
||||
\newcommand{\divisionchapter}[1]{%
|
||||
\addvspace{12pt}%
|
||||
\noindent\hfil\bfseries\color{crimson}#1\hfil\par%
|
||||
\addvspace{6pt}%
|
||||
}
|
||||
|
||||
% Adjust TOC spacing for "Chapter" prefix
|
||||
\newlength{\xtraspace}
|
||||
\settowidth{\xtraspace}{\cftchappresnum\cftchapaftersnum}
|
||||
\addtolength{\cftchapnumwidth}{\xtraspace}
|
||||
|
||||
% Unnumbered chapters with TOC entry
|
||||
\newcommand{\likechapter}[1]{%
|
||||
\chapter*{#1}
|
||||
\addcontentsline{toc}{chapter}{\textcolor{crimson}{#1}}
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% PAGE NUMBERING SYSTEM
|
||||
% =============================================================================
|
||||
% Implements traditional book numbering:
|
||||
% - Roman numerals (i, ii, iii...) for frontmatter
|
||||
% - Arabic numerals (1, 2, 3...) for mainmatter
|
||||
% Automatically switches at first numbered chapter
|
||||
\makeatletter
|
||||
\newif\if@firstnumbered%
|
||||
\@firstnumberedtrue%
|
||||
\newif\if@firstunnumbered%
|
||||
\@firstunnumberedtrue%
|
||||
|
||||
\newcounter{lastRomanPage}
|
||||
\setcounter{lastRomanPage}{1}
|
||||
|
||||
% Start document with Roman numerals (frontmatter)
|
||||
\AtBeginDocument{
|
||||
\pagenumbering{roman}
|
||||
\renewcommand{\thepage}{\roman{page}}
|
||||
}
|
||||
|
||||
% Intercept chapter command
|
||||
\let\old@chapter\chapter%
|
||||
\renewcommand{\chapter}{%
|
||||
\@ifstar{\unnumbered@chapter}{\numbered@chapter}%
|
||||
}
|
||||
|
||||
% Numbered chapters: switch to Arabic on first occurrence
|
||||
\newcommand{\numbered@chapter}[1]{%
|
||||
\if@firstnumbered%
|
||||
\cleardoublepage%
|
||||
\setcounter{lastRomanPage}{\value{page}}%
|
||||
\pagenumbering{arabic}%
|
||||
\@firstnumberedfalse%
|
||||
\else
|
||||
\setcounter{page}{\value{page}}%
|
||||
\fi
|
||||
\setcounter{sidenote}{1} % Reset footnote counter per chapter
|
||||
\old@chapter{#1}%
|
||||
}
|
||||
|
||||
% Unnumbered chapters: stay in Roman numerals
|
||||
\newcommand{\unnumbered@chapter}[1]{%
|
||||
\if@firstunnumbered%
|
||||
\clearpage
|
||||
\setcounter{lastRomanPage}{\value{page}}%
|
||||
\pagenumbering{roman}%
|
||||
\@firstunnumberedfalse%
|
||||
\fi
|
||||
\setcounter{sidenote}{1}
|
||||
\old@chapter*{#1}%
|
||||
}
|
||||
\makeatother
|
||||
|
||||
% =============================================================================
|
||||
% TABLE SIZING AND SPACING
|
||||
% =============================================================================
|
||||
% Make tables slightly smaller to fit more content
|
||||
\AtBeginEnvironment{longtable}{\scriptsize}
|
||||
|
||||
% Increase vertical spacing in table cells (default is 1.0)
|
||||
\renewcommand{\arraystretch}{1.3}
|
||||
|
||||
% Prefer placing tables at the top of pages
|
||||
\makeatletter
|
||||
\renewcommand{\fps@table}{t} % Default placement: top of page
|
||||
\makeatother
|
||||
|
||||
% =============================================================================
|
||||
% LONGTABLE PAGE BREAKING FIXES (Windows compatibility)
|
||||
% =============================================================================
|
||||
% Prevent "Infinite glue shrinkage" errors on Windows LaTeX builds
|
||||
% by giving longtable more flexibility in page breaking
|
||||
|
||||
% Allow more flexible page breaking (vs strict \flushbottom)
|
||||
\raggedbottom
|
||||
|
||||
% Process more rows before attempting page break (default is 20)
|
||||
\setcounter{LTchunksize}{50}
|
||||
|
||||
% Add extra stretch for longtable environments specifically
|
||||
\AtBeginEnvironment{longtable}{%
|
||||
\setlength{\emergencystretch}{3em}%
|
||||
\setlength{\parskip}{0pt plus 1pt}%
|
||||
}
|
||||
|
||||
% =============================================================================
|
||||
% TABLE STYLING - Clean tables with crimson borders
|
||||
% =============================================================================
|
||||
% Professional table appearance with:
|
||||
% - Clean white background (no colored rows)
|
||||
% - Crimson-colored borders
|
||||
% - Good spacing for readability
|
||||
%
|
||||
% Note: Headers are automatically bolded by Quarto when using **text** in source
|
||||
\usepackage{booktabs} % Professional table rules (\toprule, \midrule, \bottomrule)
|
||||
\usepackage{colortbl} % For colored borders (\arrayrulecolor)
|
||||
|
||||
% Global table styling - crimson borders
|
||||
\setlength{\arrayrulewidth}{0.5pt} % Thinner borders than default
|
||||
%\arrayrulecolor{crimson} % Crimson borders matching brand
|
||||
|
||||
\setcounter{chapter}{0}
|
||||
Reference in New Issue
Block a user