Skip to content

Commit

Permalink
Support generating PNG and SVG files
Browse files Browse the repository at this point in the history
  • Loading branch information
tamaracha committed Oct 21, 2024
1 parent a27535f commit 4dc99b5
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `localPackages` property in `TypstExtension` which is configured with a platform-dependent convention where local Typst packages are installed.
- `packagePath` can be set for `TypstCompileTask` which lets Gradle track changes in local package files and Typst to look for packages in the given directory. This is configured with `localPackages` from the Typst extension by default.
- Typst source set got a format section where the output formats supported by Typst can be enabled and configured. So the documents of a source set can be output in multiple formats at once.

### Changed

- `TypstSourceSet.merged` was moved to `TypstSourceSet.format.pdf.merged`.

### Removed

Expand Down
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This Gradle plugin offers a way to maintain such projects:
## Features

- [x] Compile multiple documents in parallel for faster builds
- [x] Generate all output formats supported by Typst (PDF, PNG, and SVG)
- [x] Incremental build: Edit files and rebuild only affected documents
- [x] Typst can either be automatically downloaded from GitHub releases, or use a local installation
- [x] Define multiple source sets in one project to produce variants of your content, e.g., versions for printing and web publishing
Expand Down Expand Up @@ -58,8 +59,6 @@ typst.sourceSets {
val web by registering {
// The files to compile (without .typ extension)
documents = listOf("frontmatter", "main", "appendix", "backmatter")
// Setting this creates a merged PDF file from the documents list
merged = "thesis-web-$version"
// Values set in this map are passed to Typst as --input options
inputs.put("version", version.toString())
}
Expand All @@ -78,7 +77,7 @@ In a source set folder, these subfolders are watched for changes:
- _images_: Image files included in your documents
- _typst_: Typst files, can be documents or contain declarations for importing

Running `gradlew build` now will compile all documents.
Running `gradlew build` now will compile all documents into _build/typst/<source set>/_.

### Shared sources

Expand All @@ -97,6 +96,37 @@ typst.sourceSets {
}
```

### Output formats

Currently, Typst can output a document as PDF or as a series of images in PNG or SVG format.
The desired output options can be configured per source set, e.g., PDF for printing and PNG for web publishing.

```gradle kotlin dsl
typst.sourceSets {
val web by registering {
documents = listOf("frontmatter", "main", "appendix", "backmatter")
format {
// The PNG format is right
png {
enabled = true
// Customized resolution (144 by default)
ppi = 72
}
// Disable the PDF format which is active by default
pdf.enabled = false
}
}
val printing by registering {
documents = listOf("frontmatter", "main", "poster", "appendix", "backmatter")
format {
// Setting this creates a merged PDF file from the documents list
pdf.merged = "thesis-$version"
}
}
}
```

### Images

Image files in _src/<source set>/images_ are copied to _build/generated/typst/images_.
Expand Down
8 changes: 8 additions & 0 deletions examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ typst {
create("main") {
documents.add("document")
inputs.put("gitHash", project.version.toString())
format {
pdf.enabled = true
svg.enabled = true
png {
enabled = true
ppi = 72
}
}
}
}
creationTimestamp = timestamp.get()
Expand Down
4 changes: 2 additions & 2 deletions examples/src/main/typst/document.typ
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#let gitHash = sys.inputs.at("gitHash")
#let gitHash = sys.inputs.at("gitHash", default: "")

= Test document #gitHash

#lorem(50)
#lorem(5000)
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ class GradleTypstPlugin : Plugin<Project> {
project.layout.projectDirectory.dir(project.providers.environmentVariable("APPDATA"))
}
extension.localPackages.convention(appDataDir.map { it.dir("typst/packages") })
extension.sourceSets.configureEach { s ->
s.format.pdf.enabled.convention(true)
s.format.png.enabled.convention(false)
s.format.png.ppi.convention(144)
s.format.svg.enabled.convention(false)
}
project.tasks.withType(TypstCompileTask::class.java).configureEach { task ->
task.compiler.convention(extension.compiler)
task.packagePath.set(extension.localPackages)
Expand All @@ -70,20 +76,51 @@ class GradleTypstPlugin : Plugin<Project> {
task.quality.convention(100)
}
s.images.add(convertImagesTask.flatMap { it.target })
val typstTask = project.tasks.register("compile${title}Typst", TypstCompileTask::class.java) { task ->
task.onlyIf { s.documents.get().isNotEmpty() }
task.documents.convention(s.documents.map { docs -> docs.map { typstRoot.file("$it.typ") } })
task.variables.convention(s.inputs)
task.sources.data.convention(s.data)
task.sources.fonts.convention(s.fonts)
task.sources.images.convention(s.images)
task.sources.typst.convention(s.typst)
task.destinationDir.convention(s.destinationDir)
}
val documentFilesProvider = s.documents.map { docs -> docs.map { typstRoot.file("$it.typ") } }
val typstTask = project.tasks.register("compile${title}TypstPdf", TypstCompileTask::class.java) { task ->
val format = s.format.pdf
task.onlyIf { format.enabled.get() }
task.onlyIf { s.documents.get().isNotEmpty() }
task.documents.convention(documentFilesProvider)
task.targetFilenames.convention(s.documents.map { docs -> docs.map { "$it.${format.extension}" } })
task.variables.convention(s.inputs)
task.sources.data.convention(s.data)
task.sources.fonts.convention(s.fonts)
task.sources.images.convention(s.images)
task.sources.typst.convention(s.typst)
task.destinationDir.convention(s.destinationDir.dir("pdf"))
}
project.tasks.register("merge${title}Typst", MergePDFTask::class.java) { task ->
task.onlyIf { s.merged.isPresent }
task.onlyIf { s.format.pdf.merged.isPresent }
task.documents.convention(typstTask.flatMap { it.compiled })
task.merged.convention(s.merged.zip(s.destinationDir) { name, dir -> dir.file("$name.pdf") })
task.merged.convention(s.format.pdf.merged.zip(s.destinationDir) { name, dir -> dir.file("$name.pdf") })
}
project.tasks.register("compile${title}TypstPng", TypstCompileTask::class.java) { task ->
val format = s.format.png
task.onlyIf { format.enabled.get() }
task.onlyIf { s.documents.get().isNotEmpty() }
task.documents.convention(documentFilesProvider)
task.targetFilenames.convention(s.documents.map { docs -> docs.map { "$it-{p}-of-{t}.${format.extension}" } })
task.ppi.convention(format.ppi)
task.variables.convention(s.inputs)
task.sources.data.convention(s.data)
task.sources.fonts.convention(s.fonts)
task.sources.images.convention(s.images)
task.sources.typst.convention(s.typst)
task.destinationDir.convention(s.destinationDir.dir("png"))
}
project.tasks.register("compile${title}TypstSvg", TypstCompileTask::class.java) { task ->
val format = s.format.svg
task.onlyIf { format.enabled.get() }
task.onlyIf { s.documents.get().isNotEmpty() }
task.documents.convention(documentFilesProvider)
task.targetFilenames.convention(s.documents.map { docs -> docs.map { "$it-{p}-of-{t}.${format.extension}" } })
task.variables.convention(s.inputs)
task.sources.data.convention(s.data)
task.sources.fonts.convention(s.fonts)
task.sources.images.convention(s.images)
task.sources.typst.convention(s.typst)
task.destinationDir.convention(s.destinationDir.dir("svg"))
}
}
val typstCompileTask = project.tasks.register("compileTypst") { task ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.infolektuell.gradle.typst.extensions

import org.gradle.api.Action
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Nested

abstract class TypstOutputFormatExtension {
abstract class OutputFormat {
abstract val enabled: Property<Boolean>
}
abstract class PDF : OutputFormat() {
val extension: String get() = "pdf"
abstract val merged: Property<String>
}
abstract class PNG : OutputFormat() {
val extension: String get() = "png"
abstract val ppi: Property<Int>
}
abstract class SVG : OutputFormat() {
val extension: String get() = "svg"
}
@get:Nested
abstract val pdf: PDF
fun pdf(action: Action<in PDF>) {
action.execute(pdf)
}
@get:Nested
abstract val png: PNG
fun png(action: Action<in PNG>) {
action.execute(png)
}
@get:Nested
abstract val svg: SVG
fun svg(action: Action<in SVG>) {
action.execute(svg)
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
package de.infolektuell.gradle.typst.extensions

import org.gradle.api.Action
import org.gradle.api.Named
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.*
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Nested

abstract class TypstSourceSet : Named {
abstract val destinationDir: DirectoryProperty
abstract val documents: ListProperty<String>
abstract val inputs: MapProperty<String, String>
abstract val merged: Property<String>

abstract val typst: SetProperty<Directory>
abstract val data: SetProperty<Directory>
abstract val images: SetProperty<Directory>
abstract val fonts: SetProperty<Directory>
@get:Nested
abstract val format: TypstOutputFormatExtension
fun format(action: Action<in TypstOutputFormatExtension>) {
action.execute(format)
}

fun addSourceSet(sourceSet: TypstSourceSet) {
typst.addAll(sourceSet.typst)
data.addAll(sourceSet.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ abstract class TypstCompileTask @Inject constructor(private val executor: Worker
val fontDirectories: ListProperty<Directory>
val creationTimestamp: Property<String>
val useSystemFonts: Property<Boolean>
val ppi: Property<Int>
val target: RegularFileProperty
}

Expand All @@ -46,6 +47,7 @@ abstract class TypstCompileTask @Inject constructor(private val executor: Worker
parameters.variables.get().forEach { (k, v) -> action.args("--input", "$k=$v") }
if (parameters.creationTimestamp.isPresent) action.args("--creation-timestamp", parameters.creationTimestamp.get())
if (parameters.packagePath.isPresent) action.args("--package-path", parameters.packagePath.asFile.get().absolutePath)
if (parameters.ppi.isPresent) action.args("--ppi", parameters.ppi.get().toString())
action.args(parameters.document.get().asFile.absolutePath)
.args(parameters.target.asFile.get().absolutePath)
}
Expand All @@ -59,40 +61,44 @@ abstract class TypstCompileTask @Inject constructor(private val executor: Worker
abstract val packagePath: DirectoryProperty
@get:InputFiles
abstract val documents: ListProperty<RegularFile>
@get:Input
abstract val targetFilenames: ListProperty<String>
@get:Input
abstract val root: Property<String>
@get:Input
abstract val variables: MapProperty<String, String>
@get:Optional
@get:Input
abstract val creationTimestamp: Property<String>
@get:Optional
@get:Input
abstract val ppi: Property<Int>
@get:Input
abstract val useSystemFonts: Property<Boolean>
@get:Nested
abstract val sources: SourceDirectories
@get:OutputDirectory
abstract val destinationDir: DirectoryProperty
@get:OutputFiles
val compiled: Provider<List<RegularFile>> = documents.zip(destinationDir) { docs, dest ->
docs.map { dest.file(it.asFile.nameWithoutExtension + ".pdf") }
}
val compiled: Provider<List<RegularFile>> = targetFilenames.zip(destinationDir) { docs, dir -> docs.map { dir.file(it) } }

@TaskAction
protected fun compile () {
val executable = compiler.asFileTree.matching { spec -> spec.include("**/typst", "**/typst.exe") }.singleFile.absolutePath
val queue = executor.noIsolation()
documents.get().forEach { document ->
queue.submit(TypstAction::class.java) { params ->
params.executable.set(executable)
params.packagePath.set(packagePath)
params.document.set(document)
params.root.set(root)
params.variables.set(variables)
params.fontDirectories.set(sources.fonts)
params.useSystemFonts.set(useSystemFonts)
params.creationTimestamp.set(creationTimestamp)
params.target.set(destinationDir.file(document.asFile.nameWithoutExtension + ".pdf"))
documents.get().zip(compiled.get()) { document, targetFile ->
queue.submit(TypstAction::class.java) { params ->
params.executable.set(executable)
params.packagePath.set(packagePath)
params.document.set(document)
params.root.set(root)
params.variables.set(variables)
params.fontDirectories.set(sources.fonts)
params.useSystemFonts.set(useSystemFonts)
params.creationTimestamp.set(creationTimestamp)
params.ppi.set(ppi)
params.target.set(targetFile)
}
}
}
}
}

0 comments on commit 4dc99b5

Please sign in to comment.