What would be a more suitable settings storage method?

My template has multiple themes to switch between, each with numerous configuration options such as cover images, body images, background colors, text colors, and more. I currently use variables and dictionaries to store these settings.

#let std-bibliography = bibliography
#let std-smallcaps = smallcaps
#let std-upper = upper
#let character-spacing = 0.6pt
#let smallcaps(body) = std-smallcaps(text(tracking: character-spacing, body))
#let upper(body) = std-upper(text(tracking: character-spacing, body))
// 设置封面风格
// Set cover styles
#let cover-styles = (
  mixed: "",
  sunflower: "image/cover/sunflower.svg"
)

// 设置前言风格
// Set preface styles
#let perface-styles = (
  mixed: "",
  sunflower: "image/cover/sunflower.svg"
)

// 设置正文风格
// Set content styles
#let content-styles = (
  mixed: "",
  sunflower: "image/cover/sunflower.svg"
)

And use the following code to call the settings

image(perface-styles.at(cover-style), width: 100%, height: 100%)

Are there better ways to store these settings more concisely?

My Project

Would this be better?

#let themes = (
  abyss: (
    background-color: rgb("#000000"), // 背景 / Background
    text-color: rgb("#ffffff"), // 文字颜色 / Text
    stroke-color: rgb("#4b5358"), // 描边颜色 / Stroke
    fill-color: rgb("#1f2833"), // 填充颜色 / Fill

    cover-image: "../themes/abyss/cover.svg", // 封面图片 / Cover image
    preface-image: "../themes/abyss/preface.svg", // 前言图片 / Preface image
    content-image: "../themes/abyss/content.svg", // 正文图片 / Content image
  ),
)

Or would I use database like SQLite ?

I think the approach with a dictionary is the simplest for now.

Probably you would want to have something like this:

#let img-path = "../themes/abyss/"
#let themes = (
  abyss: (
    color: (
      background: black,
      text: white,
      stroke: rgb("#4b5358"),
      fill: rgb("#1f2833"),
    ),
    image: (
      cover: img-path + "cover.svg",
      preface: img-path + "preface.svg",
      content: img-path + "content.svg",
    ),
  ),
)

#themes.abyss.color.background
#themes.abyss.image.cover

For the std- variables, you should be able to use std.element which will use the same elements. Your use however doesn’t require that, because what I would recommend instead at least for the smallcaps function is:

#show smallcaps: set text(tracking: character-spacing)

I also noticed a typo, perface-styles should be preface-styles? One more thing I would recommend about your template is to have a PDF of the manual, unless I missed that file entirely.

1 Like

My manual is still editing.

But I think using dictionaries is a bit complicated . Is it possible to construct a function like

themes(“theme”, “setting”)

Perhaps it could be implemented this way.

#let theme_setting_heading = (
    "background-color", // 主题背景颜色 / Theme background color
    "text-color", // 主题文字颜色 / Theme text color
    "stroke-color", // 主题描边颜色 / Theme stroke color
    "fill-color", // 主题填充颜色 / Theme fill color

    "cover-image", // 主题封面图片路径 / Theme cover image paths
    "preface-image", // 主题前言图片路径 / Theme preface image paths
    "content-image" // 主题图片路径 / Theme image paths
)

#let theme_settings = (
  abyss: (rgb("#000000"), rgb("#ffffff"), rgb("#4b5358"), rgb("#1f2833"), "../themes/abyss/cover.svg", "../themes/abyss/preface.svg", "../themes/abyss/content.svg"),
)

#let index(
  theme: "abyss",
  setting: "bakground-color",
) = {
  let searcher(it) = {return it = setting}
  text(theme_setting_heading.position(searcher(setting)))
}

Seems like you split the first level of dictionary into these functions that are more arrays than they are dictionaries, and you paired them together.

If it makes more sense to you, I see no problem in that. What could also be done with arrays is:

#let theme-names = (
	"first",
	"second",
	"third",
)

#let theme-values = (
	abyss: (
		1,
		2,
		3,
	),
)

#let index(theme: "abyss") = {
	theme-names.zip(theme-values.at(theme))
}

#index()
Output

But make sure to add comments about this storing somewhere, because I don’t think it’s that common.

Actually, I want to use a function to directly retrieve the value of the corresponding setting.

#let index(
  theme: "abyss", // 主题名称 / Theme name
  setting: "bakground-color", // 主题设置名称 / Theme setting name
) = {
  // 获取从`主题设置表头`主题设置对应的索引值 / Get the index value corresponding to the theme setting from the `theme setting header`
  // 根据索引值从`主题设置`字典中选定主题的设置数组中获取对应的值 / Get the corresponding value from the selected theme's settings array in the `theme settings` dictionary based on the index value
}

Sure thing, you could extend the code in my previous reply if you want returned only the chosen settings.

We make sure the arguments exist and after the array is zipped, it’s filtered for only those settings, and returned as a dictionary:

#let theme-styles = (
	"background-color",
	"text-color",
	"stroke-color",
	"fill-color",
	"cover-image",
	"preface-image",
	"cover-image",
)

#let theme-values = (
	abyss: ("1", "2", "3", "4", "5", "6", "7",),
	flowers: ("10", "20", "30", "40", "50", "60", "70",),
)

#let index(theme: "abyss", styles-to-apply: "background-color") = {
	// Force into an array
	if type(styles-to-apply) == str { styles-to-apply = (styles-to-apply,) }

	// The theme and styles exist
	if theme not in theme-values { panic("Theme not found.") }
	if styles-to-apply.any(style => style not in theme-styles) { panic("Style not found.") }

	// Apply the theme
	theme-styles.zip(theme-values.at(theme)).filter(style => style.first() in styles-to-apply).to-dict()
}

#index(theme: "flowers", styles-to-apply: "background-color")

#index(styles-to-apply: ("text-color", "background-color",))
Output

Now, if you have many themes planned, I believe this could turn out to be a better Typst-only files approach than having a rather repetitive dictionary:

(
  first-theme: (background-color: ...),
  second-theme: (background-color: ...),
  ...
)

As you initially mentioned, storing such a long and repetitive dictionary could otherwise be done outside of Typst, for example in a JSON file.

I ultimately used the following code

#let theme_setting_heading = (
    "background-color", // 主题背景颜色 / Theme background color
    "text-color", // 主题文字颜色 / Theme text color
    "stroke-color", // 主题描边颜色 / Theme stroke color
    "fill-color", // 主题填充颜色 / Theme fill color

    "cover-image", // 主题封面图片路径 / Theme cover image paths
    "preface-image", // 主题前言图片路径 / Theme preface image paths
    "content-image" // 主题图片路径 / Theme image paths
)

#let theme_settings = (
  abyss: (rgb("#000000"), rgb("#ffffff"), rgb("#4b5358"), rgb("#1f2833"), "../themes/abyss/cover.svg", "../themes/abyss/preface.svg", "../themes/abyss/content.svg"),
  light: ("", "", "", "", "", "", ""),
)

#let themes(
  theme: "abyss", // 主题名称 / Theme name
  setting: "background-color", // 主题设置名称 / Theme setting name
) = {
  let isSetting(value) = value == setting // 判断

  // 从`主题设置表头`中获取主题设置对应的索引值,并根据索引值从`主题设置`字典中选定主题的设置数组中获取对应的值 / Get the index value corresponding to the theme setting from the `theme setting header`, and use the index value to get the corresponding value from the setting array of the selected theme from the `theme settings` dictionary
  theme_settings.at(theme).at(theme_setting_heading.position(isSetting))
}

That’s fine, but I feel like something doesn’t quite add up.

  • How is position taking in isSetting which presumably is bool?

  • Can’t the setting argument be an array of strings, in which case themes would return an array of values?

I see now that you only wanted the values returned. You can easily achieve this, as well as warranting the second point I mentioned here, with my code by appending .values() to the dictionary.

If you’re satisfied with your own code, then I suggest you choose that as solved.

I’m wondering if it’s possible to merge these two lines of code into one.

#let isSetting(value) = value == setting

#theme_settings.at(theme).at(theme_setting_heading.position(isSetting))