diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/document/DocumentType.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/document/DocumentType.kt index c98e3597..137c6ae3 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/document/DocumentType.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/document/DocumentType.kt @@ -1,22 +1,25 @@ package eu.iamgio.quarkdown.document +import eu.iamgio.quarkdown.document.page.PageOrientation + /** * Type of produced document, which affects its post-rendering stage. + * @param preferredOrientation the preferred orientation of the document, to apply if not overridden by the user */ -enum class DocumentType { +enum class DocumentType(val preferredOrientation: PageOrientation) { /** * A document whose rendered content is not altered by the post-rendering stage. * Plain Markdown is often used as plain (e.g. READMEs). */ - PLAIN, + PLAIN(PageOrientation.PORTRAIT), /** * A document that is split into pages of mostly text content: books, articles, papers, etc. */ - PAGED, + PAGED(PageOrientation.PORTRAIT), /** * A slides-based document for presentations. */ - SLIDES, + SLIDES(PageOrientation.LANDSCAPE), } diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/BoundingBox.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/BoundingBox.kt new file mode 100644 index 00000000..e8af1a6f --- /dev/null +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/BoundingBox.kt @@ -0,0 +1,23 @@ +package eu.iamgio.quarkdown.document.page + +/** + * A generic bounding box with a width and a height. + */ +data class BoundingBox( + val width: Size, + val height: Size, +) { + /** + * A 90-degrees rotated version of this bounding box, + * which happens to be a new [BoundingBox] with the height and width swapped. + */ + val rotated: BoundingBox + get() = BoundingBox(height, width) +} + +/** + * Shorthand for creating a [BoundingBox] from two [Size]s. + * @param height height of the bounding box + * @return a new [BoundingBox] with [this] width and the given [height] + */ +infix fun Size.by(height: Size) = BoundingBox(this, height) diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageOrientation.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageOrientation.kt new file mode 100644 index 00000000..4dda1f45 --- /dev/null +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageOrientation.kt @@ -0,0 +1,16 @@ +package eu.iamgio.quarkdown.document.page + +/** + * The orientation of a page. + */ +enum class PageOrientation { + /** + * Vertical orientation, where `height >= width` + */ + PORTRAIT, + + /** + * Horizontal orientation, where `width > height` + */ + LANDSCAPE, +} diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageSizeFormat.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageSizeFormat.kt index 1da33f96..fd6350c7 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageSizeFormat.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/document/page/PageSizeFormat.kt @@ -2,28 +2,42 @@ package eu.iamgio.quarkdown.document.page /** * Standard page sizes. - * @param width width of the page - * @param height height of the page + * @param bounds size the page */ -enum class PageSizeFormat(val width: Size, val height: Size) { - A0(841.0.mm, 1189.0.mm), - A1(594.0.mm, 841.0.mm), - A2(420.0.mm, 594.0.mm), - A3(297.0.mm, 420.0.mm), - A4(210.0.mm, 297.0.mm), - A5(148.0.mm, 210.0.mm), - A6(105.0.mm, 148.0.mm), - A7(74.0.mm, 105.0.mm), - A8(52.0.mm, 74.0.mm), - A9(37.0.mm, 52.0.mm), - A10(26.0.mm, 37.0.mm), - B0(1000.0.mm, 1414.0.mm), - B1(707.0.mm, 1000.0.mm), - B2(500.0.mm, 707.0.mm), - B3(353.0.mm, 500.0.mm), - B4(250.0.mm, 353.0.mm), - B5(176.0.mm, 250.0.mm), - LETTER(8.5.inch, 11.0.inch), - LEGAL(8.5.inch, 14.0.inch), - LEDGER(11.0.inch, 17.0.inch), +enum class PageSizeFormat(private val bounds: BoundingBox) { + A0(841.0.mm by 1189.0.mm), + A1(594.0.mm by 841.0.mm), + A2(420.0.mm by 594.0.mm), + A3(297.0.mm by 420.0.mm), + A4(210.0.mm by 297.0.mm), + A5(148.0.mm by 210.0.mm), + A6(105.0.mm by 148.0.mm), + A7(74.0.mm by 105.0.mm), + A8(52.0.mm by 74.0.mm), + A9(37.0.mm by 52.0.mm), + A10(26.0.mm by 37.0.mm), + B0(1000.0.mm by 1414.0.mm), + B1(707.0.mm by 1000.0.mm), + B2(500.0.mm by 707.0.mm), + B3(353.0.mm by 500.0.mm), + B4(250.0.mm by 353.0.mm), + B5(176.0.mm by 250.0.mm), + LETTER(8.5.inch by 11.0.inch), + LEGAL(8.5.inch by 14.0.inch), + LEDGER(11.0.inch by 17.0.inch), + ; + + /** + * Base orientation of the format. + */ + private val orientation: PageOrientation + // Assuming width and height are declared with the same size unit. + get() = if (bounds.width.value > bounds.height.value) PageOrientation.LANDSCAPE else PageOrientation.PORTRAIT + + /** + * @param orientation orientation of the page + * @return the bounds of the format for the given orientation + * If, for instance, the document is landscape and the given format is portrait, the format is converted to landscape. + */ + fun getBounds(orientation: PageOrientation): BoundingBox = if (this.orientation == orientation) bounds else bounds.rotated } diff --git a/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Document.kt b/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Document.kt index b67d0f0f..7c8e7888 100644 --- a/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Document.kt +++ b/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Document.kt @@ -9,6 +9,7 @@ import eu.iamgio.quarkdown.document.DocumentInfo import eu.iamgio.quarkdown.document.DocumentTheme import eu.iamgio.quarkdown.document.DocumentType import eu.iamgio.quarkdown.document.page.PageMarginPosition +import eu.iamgio.quarkdown.document.page.PageOrientation import eu.iamgio.quarkdown.document.page.PageSizeFormat import eu.iamgio.quarkdown.document.page.Size import eu.iamgio.quarkdown.document.page.Sizes @@ -143,27 +144,33 @@ fun theme( /** * Sets the format of the document. * If a value is `null`, the default value supplied by the underlying renderer is used. - * @param format standard size format of each page (overrides [width] and [height]) + * If neither [format] nor [width] or [height] are `null`, the latter override the former. + * If both [format] and [width] or [height] are `null`, the default value is used. + * @param format standard size format of each page (overridden by [width] and [height]) + * @param orientation orientation of each page. + * If not specified, the preferred orientation of the document type is used. + * Does not take effect if [format] is not specified. * @param width width of each page * @param height height of each page * @param margin blank space around the content of each page. Only supported in paged mode. - * @throws IllegalArgumentException if both [format] and either [width] or [height] are not `null` */ @Name("pageformat") fun pageFormat( @Injected context: Context, @Name("size") format: PageSizeFormat? = null, + orientation: PageOrientation = context.documentInfo.type.preferredOrientation, width: Size? = null, height: Size? = null, margin: Sizes? = null, ): VoidValue { - if (format != null && (width != null || height != null)) { - throw IllegalArgumentException("Specifying a page size format overrides manual width and height") - } - with(context.documentInfo.pageFormat) { - this.pageWidth = format?.width ?: width - this.pageHeight = format?.height ?: height + // If, for instance, the document is landscape and the given format is portrait, + // the format is converted to landscape. + val formatBounds = format?.getBounds(orientation) + + // Width and/or height override the format size if both are not null. + this.pageWidth = width ?: formatBounds?.width ?: this.pageWidth + this.pageHeight = height ?: formatBounds?.height ?: this.pageHeight this.margin = margin }