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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.sif
*.tar
*.DS_Store
slides.*
29 changes: 24 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ RUN cd /slidefactory && \

FROM docker.io/debian:bookworm

ARG INCLUDE_ILLUSTRATIONS=true

ENV DEBIAN_FRONTEND=noninteractive

# General packages
Expand Down Expand Up @@ -55,11 +57,11 @@ RUN apt-get update -qy && \
apt-get clean

# Fonts
RUN FONT_DIR=NotoSans && \
RUN FONT_DIR=NunitoSans && \
mkdir -p /slidefactory/fonts/$FONT_DIR && \
wget https://github.com/notofonts/latin-greek-cyrillic/releases/download/NotoSans-v2.013/NotoSans-v2.013.zip -O tmp.zip && \
unzip -j tmp.zip 'NotoSans/googlefonts/ttf/*' -d /slidefactory/fonts/$FONT_DIR && \
unzip -j tmp.zip 'OFL.txt' -d /slidefactory/fonts/$FONT_DIR && \
wget https://github.com/googlefonts/NunitoSans/archive/refs/heads/main.zip -O tmp.zip && \
unzip -j tmp.zip 'NunitoSans-main/fonts/ttf/*' -d /slidefactory/fonts/$FONT_DIR && \
unzip -j tmp.zip 'NunitoSans-main/OFL.txt' -d /slidefactory/fonts/$FONT_DIR && \
rm tmp.zip

RUN FONT_DIR=Inconsolata && \
Expand All @@ -69,6 +71,16 @@ RUN FONT_DIR=Inconsolata && \
unzip -j tmp.zip 'Inconsolata-3.000/OFL.txt' -d /slidefactory/fonts/$FONT_DIR && \
rm tmp.zip

# CSC brand illustrations (optional, set INCLUDE_ILLUSTRATIONS=false to skip)
RUN if [ "$INCLUDE_ILLUSTRATIONS" = "true" ]; then \
mkdir -p /slidefactory/img/csc_illustrations && \
wget https://a3s.fi/swift/v1/slides/csc_illustrations.zip -O tmp.zip && \
unzip -j tmp.zip -d /slidefactory/img/csc_illustrations && \
rm tmp.zip; \
else \
echo "Skipping CSC illustrations (INCLUDE_ILLUSTRATIONS=$INCLUDE_ILLUSTRATIONS)"; \
fi

# Reveal.js
RUN wget https://github.com/hakimel/reveal.js/archive/refs/tags/4.4.0.zip -O tmp.zip && \
unzip tmp.zip 'reveal.js-4.4.0/LICENSE' -d /slidefactory && \
Expand All @@ -86,7 +98,14 @@ RUN wget https://github.com/mathjax/MathJax/archive/refs/tags/3.2.2.zip -O tmp.z
rm -f tmp.zip

# Pandoc
RUN wget https://github.com/jgm/pandoc/releases/download/2.19.2/pandoc-2.19.2-1-amd64.deb -O tmp.deb && \
RUN ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then \
wget https://github.com/jgm/pandoc/releases/download/3.8.2/pandoc-3.8.2-1-amd64.deb -O tmp.deb; \
elif [ "$ARCH" = "arm64" ]; then \
wget https://github.com/jgm/pandoc/releases/download/3.8.2/pandoc-3.8.2-1-arm64.deb -O tmp.deb; \
else \
echo "Unsupported architecture: $ARCH" && exit 1; \
fi && \
dpkg -i tmp.deb && \
rm -f tmp.deb

Expand Down
13 changes: 8 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
IMAGE_ROOT?=ghcr.io/csc-training
IMAGE=slidefactory
IMAGE_VERSION?=$(shell grep -m1 -oP '(?<=VERSION = ").+(?=")' slidefactory.py)

IMAGE_VERSION=$(shell grep -m1 'VERSION = "' slidefactory.py | sed -E 's/.*VERSION = "(.*)".*/\1/')
CONTAINER_CMD=$(shell command -v podman >/dev/null 2>&1 && echo podman || echo docker)
INCLUDE_ILLUSTRATIONS?=true

build: Dockerfile slidefactory.py
docker build \
${CONTAINER_CMD} build \
--platform "linux/amd64,linux/arm64" \
--label "org.opencontainers.image.source=https://github.com/csc-training/slidefactory" \
--label "org.opencontainers.image.description=slidefactory" \
--build-arg VERSION=${IMAGE_VERSION} \
--build-arg INCLUDE_ILLUSTRATIONS=${INCLUDE_ILLUSTRATIONS} \
-t ${IMAGE_ROOT}/${IMAGE}:${IMAGE_VERSION} \
.

push:
docker push ${IMAGE_ROOT}/${IMAGE}:${IMAGE_VERSION}
${CONTAINER_CMD} push ${IMAGE_ROOT}/${IMAGE}:${IMAGE_VERSION}

singularity:
rm -f $(IMAGE).sif $(IMAGE).tar
docker save $(IMAGE_ROOT)/$(IMAGE):$(IMAGE_VERSION) -o $(IMAGE).tar
${CONTAINER_CMD} save $(IMAGE_ROOT)/$(IMAGE):$(IMAGE_VERSION) -o $(IMAGE).tar
singularity build $(IMAGE).sif docker-archive://$(IMAGE).tar
rm -f $(IMAGE).tar

Expand Down
55 changes: 52 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,52 @@ Use help for all other options:
./slidefactory_VERSION.sif slides --help


#### Themes

Two themes are bundled:

* `csc-2026` (default) - the current CSC brand: new color palette, the
`Nunito Sans` font, and support for the [CC license badge](#license) on
the title slide.
* `csc-old` - the previous CSC look (formerly named `csc-plain`), kept for
continuity with older material.

Select a bundled theme by name by adding `--theme csc-old` with the run command.


#### Illustrations

21 CSC brand illustrations (`CSC_Characters_01.png` - `CSC_Characters_21.png`)
are available for use in slides. They are not stored in this repository -
instead they are downloaded from an external source and bundled into the
container image at build time (see `Dockerfile`), the same way fonts and
reveal.js are handled. Reference them in `slides.md` by filename, without
needing to know where slidefactory is installed:

![](csc_illustrations/CSC_Characters_01.png)

Including the illustrations in the image is optional. They are included by
default; skip them with:

make build INCLUDE_ILLUSTRATIONS=false


#### License

Add a `license` key to the YAML metadata block at the top of `slides.md`
to display a Creative Commons badge and link on the title slide (theme
`csc-2026` only):

---
title: My Slides
license: by
---

Valid values are the standard CC 4.0 license slugs: `by`, `by-sa`, `by-nd`,
`by-nc`, `by-nc-sa`, `by-nc-nd`. If `license` is left out, no license
information is shown.


#### Build pages for a project

Use pages sub-command to create an index page and convert all slides:
Expand All @@ -69,16 +115,19 @@ and follow the instructions.

### Docker / podman

The commands below work identically with `docker` or `podman` - just swap
the binary name.

Fetch the slidefactory container image:

docker pull ghcr.io/csc-training/slidefactory:VERSION
docker/podman pull ghcr.io/csc-training/slidefactory:VERSION

Convert the markdown slides to a PDF (default):

docker run -it --rm -v "$PWD:$PWD:Z" -w "$PWD" ghcr.io/csc-training/slidefactory:VERSION slides --format pdf slides.md
docker/podman run -it --rm -v "$PWD:$PWD:Z" -w "$PWD" ghcr.io/csc-training/slidefactory:VERSION slides --format pdf slides.md

All the options work the same way as for singularity
but using the above docker command instead.
but using the above docker/podman command instead.


## Known issues
Expand Down
16 changes: 8 additions & 8 deletions fonts/fonts.css
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
@font-face {
font-family: 'Noto Sans';
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(NotoSans/NotoSans-Regular.ttf) format(truetype);
src: url(NunitoSans/NunitoSans-Regular.ttf) format(truetype);
}

@font-face {
font-family: 'Noto Sans';
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(NotoSans/NotoSans-Bold.ttf) format(truetype);
src: url(NunitoSans/NunitoSans-Bold.ttf) format(truetype);
}

@font-face {
font-family: 'Noto Sans';
font-family: 'Nunito Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
src: url(NotoSans/NotoSans-Italic.ttf) format(truetype);
src: url(NunitoSans/NunitoSans-Italic.ttf) format(truetype);
}

@font-face {
font-family: 'Noto Sans';
font-family: 'Nunito Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
src: url(NotoSans/NotoSans-BoldItalic.ttf) format(truetype);
src: url(NunitoSans/NunitoSans-BoldItalic.ttf) format(truetype);
}

@font-face {
Expand Down
31 changes: 25 additions & 6 deletions slidefactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
VERSION = "3.4.3"
SLIDEFACTORY_ROOT = Path(__file__).absolute().parent
IN_CONTAINER = SLIDEFACTORY_ROOT == Path('/slidefactory')
SHARED_IMG_DPATH = SLIDEFACTORY_ROOT / 'img'

# Modify version string if this file has been edited
with open(__file__, 'rb') as f:
Expand Down Expand Up @@ -451,7 +452,7 @@ def main():
pparser_conversion = argparse.ArgumentParser(add_help=False)
pparser_conversion.add_argument(
'-t', '--theme', metavar='THEME', type=find_theme,
default='csc-plain',
default='csc-2026',
help='presentation theme name or path (default: %(default)s)')
pparser_conversion.add_argument(
'--filters', action='append', default=[],
Expand Down Expand Up @@ -596,6 +597,10 @@ def main_slides(args):
pandoc_args += ['--mathjax']
if args.format in ['html-embedded']:
pandoc_args += ['--embed-resources']
if SHARED_IMG_DPATH.is_dir():
# Let authors reference images from the shared img/ folder by
# filename only, without knowing where slidefactory is installed
pandoc_args += [f'--resource-path=.:{SHARED_IMG_DPATH}']

# Convert files
for in_fpath in args.input:
Expand All @@ -611,29 +616,43 @@ def main_slides(args):
defaults_fpath=args.defaults_fpath,
template_fpath=args.template_fpath,
pandoc_vars=pandoc_vars,
pandoc_args=pandoc_args,
filters=args.filters,
dry_run=args.dry_run,
)

if args.format == 'pdf':
# Use temporary html output for pdf
# Use temporary html output for pdf, and a temporary dir to
# resolve/copy any resource-path images used in the input
with tempfile.NamedTemporaryFile(
dir=in_fpath.parent,
prefix=f'{in_fpath.stem}-',
suffix='.html',
) as tmpfile:
) as tmpfile, \
tempfile.TemporaryDirectory(
dir=in_fpath.parent,
prefix=f'{in_fpath.stem}-media-',
) as media_dpath:
html_fpath = Path(tmpfile.name)
create_html(in_fpath, html_fpath, **html_kwargs)
create_html(in_fpath, html_fpath,
pandoc_args=pandoc_args + [f'--extract-media={media_dpath}'], # noqa: E501
**html_kwargs)
meta = read_slides_metadata(in_fpath)

# Use event name as subject if no separate subject defined
if 'subject' not in meta and 'event' in meta:
meta['subject'] = meta['event']

create_pdf(html_fpath, out_fpath, meta=meta, dry_run=args.dry_run)
elif args.format == 'html-embedded':
# --embed-resources already inlines resolved images; no
# separate media directory needed
create_html(in_fpath, out_fpath, pandoc_args=pandoc_args,
**html_kwargs)
else:
create_html(in_fpath, out_fpath, **html_kwargs)
media_dpath = out_fpath.parent / f'{out_fpath.stem}_media'
create_html(in_fpath, out_fpath,
pandoc_args=pandoc_args + [f'--extract-media={media_dpath}'], # noqa: E501
**html_kwargs)


def main_pages(args):
Expand Down
Loading