How to Implement Custom Document Types in Quarkdown: Extending Beyond Plain, Paged, Slides, and Docs
You can implement custom document types in Quarkdown by adding a new enum constant to DocumentType.kt, exposing it via the .doctype function in the standard library, and wiring any rendering-specific logic such as page formats, numbering schemes, or third-party assets.
Quarkdown ships with four built-in document types—PLAIN, PAGED, SLIDES, and DOCS—that determine default page sizing, heading numbering, and which JavaScript libraries get injected during HTML rendering. Because DocumentType is defined as a Kotlin enum, adding support for custom types like magazine or book requires modifying the core source code and updating the pipeline that consumes this type.
Understanding the DocumentType Architecture
A document type in Quarkdown is an enum constant that lives in quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt. Each type influences four critical areas of the rendering pipeline:
- Default page format – The
defaultPageFormatparameter (aPageFormatInfoobject) defines width, height, and orientation used by the HTML post-renderer and PDF generator. - Default numbering – The
defaultNumberingparameter (aDocumentNumberingobject) sets the initial formats for headings, figures, tables, and math expressions. - Function constraints – Native functions can be gated to specific types using the
@OnlyForDocumentTypeand@NotForDocumentTypeannotations. - Third-party assets – The HTML post-renderer checks
context.documentInfo.typeto decide which CSS or JavaScript bundles to include, such as Reveal.js for slides.
// DocumentType enum signature (quarkdown-core/.../DocumentType.kt)
enum class DocumentType(
val preferredOrientation: PageOrientation,
val defaultPageFormat: PageFormatInfo? = null,
val defaultNumbering: DocumentNumbering? = null,
)
Because this is a compiled enum, you cannot add types at runtime; you must extend the source and rebuild the compiler.
Step-by-Step Implementation Guide
1. Add the Enum Entry in DocumentType.kt
Create a new constant in quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt. Supply the preferredOrientation, and optionally defaultPageFormat and defaultNumbering. Insert the constant after the existing DOCS entry or alongside it.
MAGAZINE(
preferredOrientation = PageOrientation.PORTRAIT,
// 5.5" × 8.5" in points (1 inch = 72 points)
defaultPageFormat = PageFormatInfo(
pageWidth = 5.5 * 72,
pageHeight = 8.5 * 72
),
defaultNumbering = DocumentNumbering(
headings = NumberingFormat.fromString("1."),
figures = NumberingFormat.fromString("(1)"),
tables = NumberingFormat.fromString("Table (1)"),
math = NumberingFormat.fromString("(1)")
)
)
2. Expose the Type via the .doctype Function
The user-facing .doctype function is implemented in quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Document.kt. Add a branch that maps a string identifier (e.g., "magazine") to your new enum constant.
@Name("doctype")
fun doctype(
@Name("type") type: String,
@Injected context: Context
): DocumentInfo {
val docType = when (type.lowercase()) {
"plain" -> DocumentType.PLAIN
"paged" -> DocumentType.PAGED
"slides" -> DocumentType.SLIDES
"docs" -> DocumentType.DOCS
"magazine" -> DocumentType.MAGAZINE // new mapping
else -> error("Unsupported document type: $type")
}
return DocumentInfo(type = docType)
}
3. Configure HTML Rendering and Third-Party Assets
If your type requires special styling or scripts, update quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/post/document/HtmlDocumentBuilder.kt to read context.documentInfo.type.defaultPageFormat. Additionally, create a subclass of ThirdPartyLibrary in quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/post/thirdparty/ThirdPartyLibrary.kt to conditionally inject assets.
class MagazineAssets : ThirdPartyLibrary {
override fun isRequired(context: Context) =
context.documentInfo.type == DocumentType.MAGAZINE
override val scriptUrls = listOf("assets/magazine-layout.css")
}
4. Apply Function Constraints (Optional)
To restrict native functions to your new type, use the annotations defined in quarkdown-core/src/main/kotlin/com/quarkdown/core/function/reflect/annotation/. Follow the pattern used in quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Slides.kt.
@OnlyForDocumentType(DocumentType.MAGAZINE)
@Name("magazineheader")
fun magazineHeader(content: String) = // implementation
5. Add Tests and Documentation
Write unit tests in quarkdown-test/src/test/kotlin/com/quarkdown/test/DocumentTest.kt that compile a .qd file with .doctype {magazine} and assert that DocumentInfo.type equals DocumentType.MAGAZINE, that the default page format is applied, and that the correct third-party bundles are requested.
Complete Example: Adding a Magazine Document Type
Below is the full implementation of a MAGAZINE type that renders portrait documents at 5.5×8.5 inches with custom numbering.
Step A: Define the enum with custom sizing.
// quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt
enum class DocumentType(
val preferredOrientation: PageOrientation,
val defaultPageFormat: PageFormatInfo? = null,
val defaultNumbering: DocumentNumbering? = null,
) {
// ... existing types ...
MAGAZINE(
preferredOrientation = PageOrientation.PORTRAIT,
defaultPageFormat = PageFormatInfo(
pageWidth = 5.5 * 72,
pageHeight = 8.5 * 72
),
defaultNumbering = DocumentNumbering(
headings = NumberingFormat.fromString("1."),
figures = NumberingFormat.fromString("(1)"),
tables = NumberingFormat.fromString("Table (1)"),
math = NumberingFormat.fromString("(1)")
)
);
}
Step B: Map the user-facing string.
// quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Document.kt
"magazine" -> DocumentType.MAGAZINE
Step C: Register conditional assets.
// quarkdown-html/.../thirdparty/MagazineAssets.kt
class MagazineAssets : ThirdPartyLibrary {
override fun isRequired(context: Context) =
context.documentInfo.type == DocumentType.MAGAZINE
override val styleUrls = listOf("magazine.css")
}
Step D: Use it in a Quarkdown file.
.doctype {magazine}
.title {The Quarkdown Quarterly}
.author {Jane Doe}
.date {2024}
## Feature Story
This content renders with a 5.5×8.5 inch page size and custom numbering.
Running quarkdown myfile.qd now produces output using the magazine specifications.
Summary
- Add the enum constant in
DocumentType.ktwith orientation, page format, and numbering defaults. - Expose the type by adding a string mapping in the
.doctypefunction insideDocument.kt. - Wire rendering logic in
HtmlDocumentBuilder.ktandThirdPartyLibraryimplementations to handle page sizes and assets. - Gate functions using
@OnlyForDocumentTypeor@NotForDocumentTypeannotations when behavior should be type-specific. - Validate your changes with unit tests in
DocumentTest.ktthat assert correct type assignment and pipeline integration.
Frequently Asked Questions
Can I add a custom document type without modifying the Quarkdown source code?
No. Because DocumentType is implemented as a Kotlin enum in quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt, new types must be added at compile time. You cannot register custom types via plugins or configuration files; you must fork or extend the core repository and rebuild the compiler.
How do I restrict a native function to only work with my custom document type?
Annotate the function with @OnlyForDocumentType(DocumentType.YOUR_TYPE) as defined in quarkdown-core/src/main/kotlin/com/quarkdown/core/function/reflect/annotation/OnlyForDocumentType.kt. Conversely, use @NotForDocumentType to exclude your type from functions that should not support it. See quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Slides.kt for a working example of slides-only functions.
What is the difference between a document type and a page format?
A document type is a high-level classification (e.g., SLIDES, MAGAZINE) that carries semantic meaning, default numbering rules, and asset requirements. A page format is a property of the document type that defines physical dimensions (width/height in points or millimeters) and orientation. The document type provides the default page format via its defaultPageFormat parameter, but the format itself is just the geometric specification used by the HTML and PDF renderers.
How do I add custom CSS or JavaScript for my new document type?
Create a class that implements ThirdPartyLibrary in the quarkdown-html module, overriding isRequired(context) to return true only when context.documentInfo.type == DocumentType.YOUR_TYPE. Return your asset URLs in scriptUrls or styleUrls. The HtmlDocumentBuilder will automatically include these resources when building the final HTML document for that type.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →