diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/ast/quarkdown/TextTransform.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/ast/quarkdown/TextTransform.kt new file mode 100644 index 00000000..cbcc9f11 --- /dev/null +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/ast/quarkdown/TextTransform.kt @@ -0,0 +1,91 @@ +package eu.iamgio.quarkdown.ast.quarkdown + +import eu.iamgio.quarkdown.ast.NestableNode +import eu.iamgio.quarkdown.ast.Node +import eu.iamgio.quarkdown.rendering.representable.RenderRepresentable +import eu.iamgio.quarkdown.rendering.representable.RenderRepresentableVisitor +import eu.iamgio.quarkdown.visitor.node.NodeVisitor + +/** + * Text transformation a portion of text can undergo. + * If a property is set to `null` it is not specified, hence ignored. + * @param size font size + * @param weight font weight + * @param style font style + * @param decoration text decoration + * @param case text case + * @param variant font variant + */ +class TextTransformData( + val size: Size? = null, + val weight: Weight? = null, + val style: Style? = null, + val decoration: Decoration? = null, + val case: Case? = null, + val variant: Variant? = null, +) { + enum class Size : RenderRepresentable { + TINY, + SMALL, + NORMAL, + MEDIUM, + LARGE, + ; + + override fun accept(visitor: RenderRepresentableVisitor): T = visitor.visit(this) + } + + enum class Weight : RenderRepresentable { + NORMAL, + BOLD, + ; + + override fun accept(visitor: RenderRepresentableVisitor): T = visitor.visit(this) + } + + enum class Style : RenderRepresentable { + NORMAL, + ITALIC, + ; + + override fun accept(visitor: RenderRepresentableVisitor): T = visitor.visit(this) + } + + enum class Decoration : RenderRepresentable { + NONE, + UNDERLINE, + STRIKETHROUGH, + ; + + override fun accept(visitor: RenderRepresentableVisitor): T = visitor.visit(this) + } + + enum class Case : RenderRepresentable { + NONE, + UPPERCASE, + LOWERCASE, + CAPITALIZE, + ; + + override fun accept(visitor: RenderRepresentableVisitor): T = visitor.visit(this) + } + + enum class Variant : RenderRepresentable { + NORMAL, + SMALL_CAPS, + ; + + override fun accept(visitor: RenderRepresentableVisitor): T = visitor.visit(this) + } +} + +/** + * A portion of text with a specific visual transformation. + * @param data transformation the text undergoes + */ +data class TextTransform( + val data: TextTransformData, + override val children: List, +) : NestableNode { + override fun accept(visitor: NodeVisitor): T = visitor.visit(this) +} diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/BaseHtmlNodeRenderer.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/BaseHtmlNodeRenderer.kt index 620a6f1b..75c3326b 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/BaseHtmlNodeRenderer.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/BaseHtmlNodeRenderer.kt @@ -40,6 +40,7 @@ import eu.iamgio.quarkdown.ast.quarkdown.PageCounterInitializer import eu.iamgio.quarkdown.ast.quarkdown.PageMarginContentInitializer import eu.iamgio.quarkdown.ast.quarkdown.SlidesConfigurationInitializer import eu.iamgio.quarkdown.ast.quarkdown.Stacked +import eu.iamgio.quarkdown.ast.quarkdown.TextTransform import eu.iamgio.quarkdown.context.Context import eu.iamgio.quarkdown.context.resolveOrFallback import eu.iamgio.quarkdown.rendering.UnsupportedRenderException @@ -246,4 +247,6 @@ open class BaseHtmlNodeRenderer(context: Context) : TagNodeRenderer { override fun visit(transition: Transition.Style) = transition.kebabCaseName override fun visit(speed: Transition.Speed) = speed.kebabCaseName + + override fun visit(size: TextTransformData.Size) = + when (size) { + TextTransformData.Size.TINY -> "0.5em" + TextTransformData.Size.SMALL -> "0.75em" + TextTransformData.Size.NORMAL -> "1em" + TextTransformData.Size.MEDIUM -> "1.5em" + TextTransformData.Size.LARGE -> "2em" + } + + override fun visit(weight: TextTransformData.Weight) = weight.kebabCaseName + + override fun visit(style: TextTransformData.Style) = style.kebabCaseName + + override fun visit(decoration: TextTransformData.Decoration) = decoration.kebabCaseName + + override fun visit(case: TextTransformData.Case) = case.kebabCaseName + + override fun visit(variant: TextTransformData.Variant) = variant.kebabCaseName } /** diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/QuarkdownHtmlNodeRenderer.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/QuarkdownHtmlNodeRenderer.kt index 956ce2f9..27419e1f 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/QuarkdownHtmlNodeRenderer.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/html/QuarkdownHtmlNodeRenderer.kt @@ -12,6 +12,7 @@ import eu.iamgio.quarkdown.ast.quarkdown.PageCounterInitializer import eu.iamgio.quarkdown.ast.quarkdown.PageMarginContentInitializer import eu.iamgio.quarkdown.ast.quarkdown.SlidesConfigurationInitializer import eu.iamgio.quarkdown.ast.quarkdown.Stacked +import eu.iamgio.quarkdown.ast.quarkdown.TextTransform import eu.iamgio.quarkdown.context.Context import eu.iamgio.quarkdown.document.DocumentType import eu.iamgio.quarkdown.rendering.tag.buildTag @@ -105,6 +106,19 @@ class QuarkdownHtmlNodeRenderer(context: Context) : BaseHtmlNodeRenderer(context // Math is processed by the MathJax library which requires text delimiters instead of tags. override fun visit(node: MathSpan) = INLINE_MATH_FENCE + "$" + node.expression + "$" + INLINE_MATH_FENCE + override fun visit(node: TextTransform) = + buildTag("span") { + +node.children + style { + "font-size" value node.data.size + "font-weight" value node.data.weight + "font-style" value node.data.style + "font-variant" value node.data.variant + "text-decoration" value node.data.decoration + "text-transform" value node.data.case + } + } + // Invisible nodes override fun visit(node: PageMarginContentInitializer) = diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/representable/RenderRepresentableVisitor.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/representable/RenderRepresentableVisitor.kt index 5bde59bd..2cd22817 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/representable/RenderRepresentableVisitor.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/rendering/representable/RenderRepresentableVisitor.kt @@ -2,6 +2,7 @@ package eu.iamgio.quarkdown.rendering.representable import eu.iamgio.quarkdown.ast.quarkdown.Clipped import eu.iamgio.quarkdown.ast.quarkdown.Stacked +import eu.iamgio.quarkdown.ast.quarkdown.TextTransformData import eu.iamgio.quarkdown.document.page.PageMarginPosition import eu.iamgio.quarkdown.document.page.Size import eu.iamgio.quarkdown.document.page.Sizes @@ -33,4 +34,16 @@ interface RenderRepresentableVisitor { fun visit(transition: Transition.Style): T fun visit(speed: Transition.Speed): T + + fun visit(size: TextTransformData.Size): T + + fun visit(weight: TextTransformData.Weight): T + + fun visit(style: TextTransformData.Style): T + + fun visit(decoration: TextTransformData.Decoration): T + + fun visit(case: TextTransformData.Case): T + + fun visit(variant: TextTransformData.Variant): T } diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/visitor/node/NodeVisitor.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/visitor/node/NodeVisitor.kt index 90e5e973..639275ad 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/visitor/node/NodeVisitor.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/visitor/node/NodeVisitor.kt @@ -40,6 +40,7 @@ import eu.iamgio.quarkdown.ast.quarkdown.PageCounterInitializer import eu.iamgio.quarkdown.ast.quarkdown.PageMarginContentInitializer import eu.iamgio.quarkdown.ast.quarkdown.SlidesConfigurationInitializer import eu.iamgio.quarkdown.ast.quarkdown.Stacked +import eu.iamgio.quarkdown.ast.quarkdown.TextTransform /** * A visitor for [eu.iamgio.quarkdown.ast.Node]s. @@ -130,6 +131,8 @@ interface NodeVisitor { fun visit(node: MathSpan): T + fun visit(node: TextTransform): T + // Quarkdown invisible nodes fun visit(node: PageMarginContentInitializer): T diff --git a/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt b/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt index 49452f05..41f31717 100644 --- a/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt +++ b/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt @@ -1,7 +1,10 @@ package eu.iamgio.quarkdown.stdlib import eu.iamgio.quarkdown.ast.Code +import eu.iamgio.quarkdown.ast.InlineMarkdownContent import eu.iamgio.quarkdown.ast.MarkdownContent +import eu.iamgio.quarkdown.ast.quarkdown.TextTransform +import eu.iamgio.quarkdown.ast.quarkdown.TextTransformData import eu.iamgio.quarkdown.context.MutableContext import eu.iamgio.quarkdown.function.reflect.Injected import eu.iamgio.quarkdown.function.reflect.Name @@ -15,10 +18,34 @@ import eu.iamgio.quarkdown.util.toPlainText */ val Text: Module = setOf( + ::text, ::code, ::loremIpsum, ) +/** + * Creates an inline text node with specified formatting and transformation. + * @param size font size, or default if not specified + * @param weight font weight, or default if not specified + * @param style font style, or default if not specified + * @param decoration text decoration, or default if not specified + * @param case text case, or default if not specified + * @param variant font variant, or default if not specified + */ +fun text( + size: TextTransformData.Size? = null, + weight: TextTransformData.Weight? = null, + style: TextTransformData.Style? = null, + decoration: TextTransformData.Decoration? = null, + case: TextTransformData.Case? = null, + variant: TextTransformData.Variant? = null, + content: InlineMarkdownContent, +): NodeValue = + TextTransform( + TextTransformData(size, weight, style, decoration, case, variant), + content.children, + ).wrappedAsValue() + /** * Creates a code block. Contrary to its standard Markdown implementation with backtick/tilde fences, * this function accepts Markdown content as its body, hence it can be used - for example -