diff --git a/.gitignore b/.gitignore index a6698f5..ee97b28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Ignore PDF files -*.pdf \ No newline at end of file +*.pdf +/tests/temp \ No newline at end of file diff --git a/Makefile b/Makefile index 9a8d15a..2cf8b4f 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,18 @@ module: cp ./LICENSE $(TARGET_DIR)/ cp -r ./src/* $(TARGET_DIR)/src/ awk '{gsub("https://typst.app/universe/package/$(PACKAGE_NAME)", "https://github.com/Typsium/$(PACKAGE_NAME)");print}' ./README.md > $(TARGET_DIR)/README.md + + + +bump-minor: + @current_version=$$(grep '^version' typst.toml | awk -F ' = ' '{print $$2}' | tr -d '"'); \ + new_version=$$(echo $$current_version | awk -F. '{printf "%d.%d.%d", $$1, $$2+1, $$3}'); \ + sed -i '' "s|^version = .*|version = \"$$new_version\"|" typst.toml; \ + sed -i '' "s|@preview/typsium:$$current_version|@preview/typsium:$$new_version|" README.md; \ + echo "Version bumped to $$new_version" +bump-patch: + @current_version=$$(grep '^version' typst.toml | awk -F ' = ' '{print $$2}' | tr -d '"'); \ + new_version=$$(echo $$current_version | awk -F. '{printf "%d.%d.%d", $$1, $$2, $$3+1}'); \ + sed -i '' "s|^version = .*|version = \"$$new_version\"|" typst.toml; \ + sed -i '' "s|@preview/typsium:$$current_version|@preview/typsium:$$new_version|" README.md; \ + echo "Version bumped to $$new_version" \ No newline at end of file diff --git a/README.md b/README.md index 87a9881..d149faa 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,56 @@ [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Typsium/typsium/blob/main/LICENSE) ![User Manual](https://img.shields.io/badge/manual-.pdf-purple) -# Typst Chemical Formula Package +# Write beautiful chemical formulas and reactions with Typsium +## Usage +```typst +#import "@preview/typsium:0.3.0":* +``` +Enter your chemical formula or reaction into the `#ce"` method like this: +```typst +#ce("[Cu(H2O)4]^2+ + 4NH3 -> [Cu(NH3)4]^2+ + 4H2O") +``` +![result](https://raw.githubusercontent.com/Typsium/typsium/main/tests/README-graphic1/ref/1.png) -A Typst package for typesetting chemical formulas, currently working on inorganic. +You can also embed any kind of content into your chemical reactions like by using square brackets instead of a passing in a string. This will also apply any styling to the reaction. -- Typeset chemical formulas with ease -- Reactions and equations, including reversible reactions -- Support for complex reaction conditions (e.g. temperature (T=), pressure (P=), etc.) +> **Warning:** Currently, brackets inside another bracket will not be parsed correctly. -## Usage +```typst +#ce[...] +``` + +![result2](https://raw.githubusercontent.com/Typsium/typsium/main/tests/README-graphic1/ref/1.png) + +There are many different kinds of arrows to choose from. +```typst +#ce[->]\ +#ce[=>]\ +#ce[<=>]\ +#ce[<=]\ +#ce("<->")\ +#ce("<-")\ +``` + +And you can add additional arguments to them (such as the top or bottom text) by adding square brackets. + +```typst +#ce("->[top text][bottom text]") +``` + +The molecule parsing is flexible and allows many different ways of writing, so you can just copy paste in your formulas and they will probably work. Oxidation numbers can be added like this`^^`, radicals can be added like this`.` and hydration groups can be added like this`*`. -To use Typsium, you need to include the package in your document: -// update to newest typsium usage, add "#import "@preview/typsium:0.3.0": ce" when releasing 0.3.0 ```typst -#ce("[Cu(H2O)4] 2+ + 4NH3 -> [Cu(NH3)4] 2+ + 4H2O") +//examples +``` + +You can use many kinds of brackets. they will auto scale by default, but you can disable it with a show rule. +```typst +//brackets examples and grow-brackets show rule +``` +Inline formulas often need to be a bit more compact, for this purpose there is an `affect-layout` rule, which can be toggled on and off for each part of the reaction separately. +```typst +//brackets examples ``` -![result](https://raw.githubusercontent.com/Typsium/typsium/main/tests/README-graphic1/ref/formula-parser.svg) +You can use Typsium inside other packages and the styling will be consistent across the entire document. \ No newline at end of file diff --git a/src/data-model.typ b/src/data-model.typ deleted file mode 100644 index 42547be..0000000 --- a/src/data-model.typ +++ /dev/null @@ -1,253 +0,0 @@ -#import "utils.typ": ( - is-sequence, - is-kind, - is-heading, - is-metadata, - padright, - get-all-children, - hydrates, - elements, - get-element-dict, - get-molecule-dict, - to-string, -) -#import "regex.typ": patterns - -#let get-element( - symbol: auto, - atomic-number: auto, - common-name: auto, - cas: auto, -) = { - let element = if symbol != auto { - elements.find(x => x.symbol == symbol) - } else if atomic-number != auto { - elements.find(x => x.atomic-number == atomic-number) - } else if common-name != auto { - elements.find(x => x.common-name == common-name) - } else if cas != auto { - elements.find(x => x.cas == cas) - } - return metadata(element) -} - -#let validate-element(element) = { - let type = type(element) - if type == str { - if element.len() > 2 { - return get-element(common-name: element) - } else { - return get-element(symbol: element) - } - } else if type == int { - return get-element(atomic-number: element) - } else if type == content { - return get-element-dict(element) - } else if type == dictionary { - return element - } -} - -//TODO: properly parse bracket contents -// maybe recursively with a bracket regex, passing in the bracket content and multiplier(?) -//TODO: Properly apply stochiometry -#let get-element-counts(molecule) = { - let found-elements = (:) - let remaining = molecule.trim() - while remaining.len() > 0 { - let match = remaining.match(patterns.element) - if match != none { - remaining = remaining.slice(match.end) - let element = match.captures.at(0) - let count = 1 //int(if match.captures.at(1, default: "") == "" {1} else{match.captures.at(1)}) - let current = found-elements.at(element, default: 0) - found-elements.insert(element, count) - } else { - let char-len = remaining.codepoints().at(0).len() - - remaining = remaining.slice(char-len) - } - } - return found-elements -} - -#let get-weight(molecule) = { - let element = get-element-dict(molecule) - molecule = get-molecule-dict(molecule) - if type(element) == dictionary and element.at("atomic-weight", default: none) != none { - return element.atomic-weight - } - let weight = 0 - for value in molecule.elements { - let element = elements.find(x => x.symbol == value.at(0)) - - weight += element.atomic-weight * value.at(1) - } - return weight -} - -#let define-ion( - element, - charge: 0, - delta: 0, - override-common-name: none, - override-iupac-name: none, - override-CAS: none, - override-h-p: none, - override-ghs: none, - validate: true, -) = { - if validate { - element = validate-element(element) - } - element = if charge != 0 { - element.charge = charge - } else { - element.charge = element.at("charge", default: 0) + delta - } - return element -} - -#let define-isotope( - element, - mass-number, - override-atomic-weight: none, - override-common-name: none, - override-iupac-name: none, - override-cas: none, - override-h-p: none, - override-ghs: none, - validate: true, -) = { - if validate { - element = validate-element(element) - } - - element.mass-number = mass-number - if override-atomic-weight != none { - element.atomic-weight = override-atomic-weight - } - if override-common-name != none { - element.common-name = override-common-name - } - if override-iupac-name != none { - element.iupac-name = override-iupac-name - } - if override-cas != none { - element.override-cas = override-cas - } - if override-common-name != none { - element.common-name = override-common-name - } - if override-common-name != none { - element.common-name = override-common-name - } - if override-common-name != none { - element.common-name = override-common-name - } - return element -} - -#let define-molecule( - common-name: none, - iupac-name: none, - formula: "", - smiles: "", - inchi: "", - cas: "", - h-p: (), - ghs: (), - validate: true, -) = { - let found-elements - if validate { - // TODO: continue to add more validation as we go - // things should fail here instead of causing errors down the line - if common-name == none { - common-name = formula - } - - if smiles == "" { - smiles == none - } else if formula == "" { - //TODO: actually calculate the formula based on the smiles code (don't forget to add H on Carbon atoms) - formula = smiles - } - - if cas == "" { - cas = none - } - - found-elements = get-element-counts(formula) - - if inchi != "" { - // TODO: create InChI keys from provided InChI: - // https://typst.app/universe/package/jumble - // https://www.inchi-trust.org/download/104/InChI_TechMan.pdf - } else { - inchi = none - } - } - - return metadata(( - kind: "molecule", - common-name: common-name, - iupac-name: iupac-name, - formula: formula, - smiles: smiles, - inchi: inchi, - cas: cas, - h-p: h-p, - ghs: ghs, - elements: found-elements, - )) -} - -#let define-hydrate( - molecule, - amount: 1, - override-common-name: none, - override-iupac-name: none, - override-smiles: none, - override-inchi: none, - override-cas: none, - override-h-p: none, - override-ghs: none, -) = { - molecule = get-molecule-dict(molecule) - define-molecule( - common-name: if override-common-name != none { override-common-name } else { - molecule.common-name + sym.space + hydrates.at(amount) - }, - iupac-name: if override-iupac-name != none { override-iupac-name } else { - molecule.iupac-name + sym.semi + hydrates.at(amount) - }, - formula: molecule.formula + sym.space.narrow + sym.dot + sym.space.narrow + str(amount) + "H2O", - smiles: if override-smiles != none { override-smiles } else { molecule.smiles }, - inchi: if override-inchi != none { override-inchi } else { molecule.inchi }, - cas: if override-cas != none { override-cas } else { molecule.cas }, - h-p: if override-h-p != none { override-h-p } else { molecule.h-p }, - ghs: if override-ghs != none { override-ghs } else { molecule.ghs }, - ) -} - -#let reaction(body) = { - let children = get-all-children(body) - - // repr(body) - - // linebreak() - let result = "" - for child in children { - if is-metadata(child) { - if is-kind(child, "molecule") { - result += child.value.formula - } else if is-kind(child, "element") { - result += child.value.symbol - } - } else { - result += child - } - } - ce(to-string(result)) -} diff --git a/src/display-intermediate-representation.typ b/src/display-intermediate-representation.typ deleted file mode 100644 index c55c1a4..0000000 --- a/src/display-intermediate-representation.typ +++ /dev/null @@ -1,112 +0,0 @@ -#import "utils.typ": try-at, count-to-content, charge-to-content, get-bracket, get-arrow, phase-to-content - -#let display-element(data) = { - let isotope = data.at("isotope", default: none) - math.attach( - data.symbol, - t: data.at("oxidation-number", default: none), - tr: charge-to-content(data.at("charge", default: none), radical: data.at("radical", default: false)), - br: count-to-content(data.at("count", default: none)), - tl: try-at(isotope, "mass-number"), - bl: try-at(isotope, "atomic-number"), - ) -} - -#let display-group(data) = { - let children = data.at("children", default: ()) - let kind = data.at("kind", default: 1) - math.attach( - math.lr({ - get-bracket(kind, open: true) - for child in children { - if child.type == "content" { - child.body - } else if child.type == "element" { - display-element(child) - } else if data.type == "align" { - $&$ - } else if child.type == "group" { - display-group(child) - } - } - get-bracket(kind, open: false) - }), - tr: charge-to-content(data.at("charge", default: none)), - br: count-to-content(data.at("count", default: none)), - ) -} - -#let display-molecule(data) = { - count-to-content(data.at("count", default: none)) - math.attach( - [ - #let children = data.at("children", default: ()) - #for child in children { - if child.type == "content" { - child.body - } else if data.type == "align" { - $&$ - } else if child.type == "element" { - display-element(child) - } else if child.type == "group" { - display-group(child) - } - } - ], - tr: charge-to-content(data.at("charge", default: none)), - // br: phase-to-content(data.at("phase", default:none)), - ) - context { - text(phase-to-content(data.at("phase", default: none)), size: text.size * 0.75) - } -} - -#let display-ir(data) = { - if data == none { - none - } else if type(data) == array { - for value in data { - display-ir(value) - //this removes spacing for groups that have long charges (looks better) - if value.type == "molecule" { - let last = value.children.last() - if ( - last.type == "group" and (last.at("charge", default: none) != none or last.at("count", default: none) != none) - ) { - h(-0.4em) - } - } - } - } else if type(data) == dictionary { - if data.type == "molecule" { - display-molecule(data) - } else if data.type == "+" { - h(0.4em, weak: true) - math.plus - h(0.4em, weak: true) - } else if data.type == "group" { - display-group(data) - } else if data.type == "element" { - display-element(data) - } else if data.type == "content" { - data.body - } else if data.type == "align" { - $&$ - } else if data.type == "arrow" { - h(0.4em, weak: true) - let top = display-ir(data.at("top", default: none)) - let bottom = display-ir(data.at("bottom", default: none)) - math.attach( - math.stretch( - get-arrow(data.at("kind", default: 0)), - size: 100% + 2em, - ), - t: top, - b: bottom, - ) - h(0.4em, weak: true) - } - } else if type(data) == content { - data - } -} diff --git a/src/display-shell-configuration.typ b/src/display-shell-configuration.typ deleted file mode 100644 index a78cdf3..0000000 --- a/src/display-shell-configuration.typ +++ /dev/null @@ -1,76 +0,0 @@ -#import "utils.typ": get-element-dict, shell-capacities, orbital-capacities - -#let get-shell-configuration(element) = { - element = get-element-dict(element) - let charge = element.at("charge", default: 0) - let electron-amount = element.atomic-number - charge - - let result = () - for value in shell-capacities { - if electron-amount <= 0 { - break - } - - if electron-amount >= value.at(1) { - result.push(value) - electron-amount -= value.at(1) - } else { - result.push((value.at(0), electron-amount)) - electron-amount = 0 - } - } - return result -} - -//TODO: fix Cr and Mo -#let get-electron-configuration(element) = { - element = get-element-dict(element) - let charge = element.at("charge", default: 0) - let electron-amount = element.atomic-number - charge - - let result = () - for value in orbital-capacities { - if electron-amount <= 0 { - break - } - if electron-amount >= value.at(1) { - result.push(value) - electron-amount -= value.at(1) - } else { - result.push((value.at(0), electron-amount)) - electron-amount = 0 - } - } - return result -} - -#let display-electron-configuration(element, short: false) = { - let configuration = get-electron-configuration(element) - - if short { - let prefix = "" - if configuration.at(14, default: (0, 0)).at(1) == 6 { - configuration = configuration.slice(15) - prefix = "[Rn]" - } else if configuration.at(10, default: (0, 0)).at(1) == 6 { - configuration = configuration.slice(11) - prefix = "[Xe]" - } else if configuration.at(7, default: (0, 0)).at(1) == 6 { - configuration = configuration.slice(8) - prefix = "[Kr]" - } else if configuration.at(4, default: (0, 0)).at(1) == 6 { - configuration = configuration.slice(5) - prefix = "[Ar]" - } else if configuration.at(2, default: (0, 0)).at(1) == 6 { - configuration = configuration.slice(3) - prefix = "[Ne]" - } else if configuration.at(0, default: (0, 0)).at(1) == 2 { - configuration = configuration.slice(1) - prefix = "[He]" - } - - return prefix + configuration.map(x => $#x.at(0)^#str(x.at(1))$).sum() - } else { - return configuration.map(x => $#x.at(0)^#str(x.at(1))$).sum() - } -} diff --git a/src/lib.typ b/src/lib.typ index 5fae9b0..d2d01fa 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,12 +1,31 @@ -#import "data-model.typ": get-element-counts, get-element, get-weight, define-molecule, define-hydrate, reaction -#import "display-shell-configuration.typ": ( - get-electron-configuration, - get-shell-configuration, - display-electron-configuration, -) -#import "display-intermediate-representation.typ": display-ir -#import "parse-formula-intermediate-representation.typ": string-to-ir +// #import "data-model.typ": get-element-counts, get-element, get-weight, define-molecule, define-hydrate +// #import "display-shell-configuration.typ": get-electron-configuration,get-shell-configuration,display-electron-configuration, +#import "parse-formula-intermediate-representation.typ": string-to-reaction +#import "parse-content-intermediate-representation.typ": content-to-reaction +#import "typing.typ": set-arrow, set-element, set-group, set-molecule, set-reaction, elembic, fields, selector +#import "model/arrow-element.typ": arrow +#import "model/element-element.typ": element +#import "model/group-element.typ": group +#import "model/molecule-element.typ": molecule +#import "model/reaction-element.typ": reaction #let ce(formula) = { - display-ir(string-to-ir(formula)) + + show "*": sym.dot + + if type(formula) == str{ + let result = string-to-reaction(formula) + if result.len() == 1{ + result.at(0) + } else { + reaction(result) + } + } else if type(formula) == content{ + let result = content-to-reaction(formula) + if result.len() == 1{ + result.at(0) + } else { + reaction(result) + } + } } diff --git a/src/model/arrow-element.typ b/src/model/arrow-element.typ new file mode 100644 index 0000000..f9c88fa --- /dev/null +++ b/src/model/arrow-element.typ @@ -0,0 +1,39 @@ +#import "@preview/elembic:1.1.0" as e + +#import "../utils.typ": ( + get-arrow, +) + +#let arrow( + kind: 0, + top: (), + bottom: (), +) = { } + +#let draw-arrow(it) = { + math.attach( + math.stretch( + get-arrow(it.kind), + size: 100% + 2em, + ), + t: for top-child in it.top { + top-child + }, + b: for bottom-child in it.bottom { + bottom-child + }, + ) +} + +#let arrow = e.element.declare( + "arrow", + prefix: "@preview/typsium:0.3.0", + + display: draw-arrow, + + fields: ( + e.field("kind", int, default: 0), + e.field("top", e.types.array(e.types.any), default: ()), + e.field("bottom", e.types.array(e.types.any), default: ()), + ), +) diff --git a/src/model/element-element.typ b/src/model/element-element.typ new file mode 100644 index 0000000..01dd19a --- /dev/null +++ b/src/model/element-element.typ @@ -0,0 +1,92 @@ +#import "@preview/elembic:1.1.0" as e +#import "../utils.typ": ( + count-to-content, + charge-to-content, + oxidation-to-content, + none-coalesce, + customizable-attach, +) + +#let element( + symbol: "", + count: 1, + charge: 0, + oxidation: none, + a: none, + z: none, + rest: none, + radical: false, + affect-layout: true, + roman-oxidation: true, + roman-charge: false, + radical-symbol: sym.dot, + negative-symbol: math.minus, + positive-symbol: math.plus, +) = { } + +#let draw-element(it) = { + let base = it.symbol + if it.rest != none { + if type(it.rest) == content { + base += it.rest + } else if type(it.rest) == int { + base += box['] * it.rest + } + } + + let mass-number = it.a + if type(it.a) == int { + mass-number = [#it.a] + } + let atomic-number = it.z + if type(it.z) == int { + atomic-number = [#it.z] + } + + customizable-attach( + base, + t: oxidation-to-content( + it.oxidation, + roman: it.roman-oxidation, + negative-symbol: it.negative-symbol, + positive-symbol: it.positive-symbol, + ), + tr: charge-to-content( + it.charge, + radical: it.radical, + roman: it.roman-charge, + radical-symbol: it.radical-symbol, + negative-symbol: it.negative-symbol, + positive-symbol: it.positive-symbol, + ), + br: count-to-content(it.count), + tl: mass-number, + bl: atomic-number, + affect-layout: it.affect-layout, + ) +} +} + +#let element = e.element.declare( + "element", + prefix: "@preview/typsium:0.3.0", + + display: draw-element, + + fields: ( + e.field("symbol", e.types.union(str, content), default: none, required: true), + e.field("count", e.types.union(int, content), default: 1), + e.field("charge", e.types.union(int, content), default: 0), + e.field("oxidation", e.types.union(int, content), default: none), + e.field("a", e.types.union(int, content), default: none), + e.field("z", e.types.union(int, content), default: none), + e.field("rest", e.types.union(int, content), default: none), + e.field("radical", bool, default: false), + e.field("affect-layout", bool, default: true), + e.field("roman-oxidation", bool, default: true), + e.field("roman-charge", bool, default: false), + e.field("radical-symbol", content, default: sym.dot), + e.field("negative-symbol", content, default: math.minus), + e.field("positive-symbol", content, default: math.plus), + ), +) diff --git a/src/model/element-variable.typ b/src/model/element-variable.typ new file mode 100644 index 0000000..87f1c3b --- /dev/null +++ b/src/model/element-variable.typ @@ -0,0 +1,235 @@ +#import "@preview/elembic:1.1.0" as e +#import "../utils.typ": ( + // is-sequence, + // is-kind, + // is-heading, + // is-metadata, + // padright, + // get-all-children, + // hydrates, + // elements, + // get-element-dict, + // get-molecule-dict, + // to-string, +) + + + +#let element-variable = e.types.declare( + "element-variable", + prefix: "@preview/typsium:0.3.0", + fields: ( + e.field("symbol", str, doc: "The symbol in the periodic table. For example: H, He, Li,..."), + e.field("common-name", str, doc: "The name of the element."), + e.field("atomic-number", int, doc: "Atomic number (Z) of the element / number of protons."), + e.field("most-common-isotope",int,doc: "Mass number (A) of the most common isotope / number of protons + neutrons."), + e.field("group", int, doc: "The column of the element inside the periodic table. ranges 1-18."), + e.field("period", int, doc: "The period of the element inside the periodic table."), + e.field("block", int, doc: "Is the element s-block, f-block, d-block or p-block?"), + e.field("atomic-weight", int, doc: "The average of the weights of all isotopes of the element."), + e.field("covalent-radius", int, doc: "Covalent radius of the element."), + e.field("van-der-waal-radius", int, doc: "Van der Waals radius of the element."), + e.field("outshell-electrons", int, doc: "The number of electrons in the outermost shell."), + e.field("density", int, doc: "The density of the pure element in kg/m^3"), + e.field("melting-point", int, doc: "The melting point of the element under standard conditions."), + e.field("boiling-point", int, doc: "The boiling point of the element under standard conditions."), + e.field("electronegativity", int, doc: "The Electronegativify of the element."), + e.field("phase", int, doc: "The boiling point of the element under standard conditions."), + e.field("cas", int, doc: "The CAS number of the pure element"), + ), +) + + +#let get-element( + symbol: auto, + atomic-number: auto, + common-name: auto, + cas: auto, +) = { + let element = if symbol != auto { + elements.find(x => x.symbol == symbol) + } else if atomic-number != auto { + elements.find(x => x.atomic-number == atomic-number) + } else if common-name != auto { + elements.find(x => x.common-name == common-name) + } else if cas != auto { + elements.find(x => x.cas == cas) + } + return metadata(element) +} + +#let validate-element(element) = { + let type = type(element) + if type == str { + if element.len() > 2 { + return get-element(common-name: element) + } else { + return get-element(symbol: element) + } + } else if type == int { + return get-element(atomic-number: element) + } else if type == content { + return get-element-dict(element) + } else if type == dictionary { + return element + } +} + +//TODO: properly parse bracket contents +// maybe recursively with a bracket regex, passing in the bracket content and multiplier(?) +//TODO: Properly apply stochiometry +#let get-element-counts(molecule) = { + let found-elements = (:) + let remaining = molecule.trim() + while remaining.len() > 0 { + let match = remaining.match(patterns.element) + if match != none { + remaining = remaining.slice(match.end) + let element = match.captures.at(0) + let count = 1 //int(if match.captures.at(1, default: "") == "" {1} else{match.captures.at(1)}) + let current = found-elements.at(element, default: 0) + found-elements.insert(element, count) + } else { + let char-len = remaining.codepoints().at(0).len() + + remaining = remaining.slice(char-len) + } + } + return found-elements +} + +#let define-ion( + element, + charge: 0, + delta: 0, + override-common-name: none, + override-iupac-name: none, + override-CAS: none, + override-h-p: none, + override-ghs: none, + validate: true, +) = { + if validate { + element = validate-element(element) + } + element = if charge != 0 { + element.charge = charge + } else { + element.charge = element.at("charge", default: 0) + delta + } + return element +} + +#let define-isotope( + element, + mass-number, + override-atomic-weight: none, + override-common-name: none, + override-iupac-name: none, + override-cas: none, + override-h-p: none, + override-ghs: none, + validate: true, +) = { + if validate { + element = validate-element(element) + } + + element.mass-number = mass-number + if override-atomic-weight != none { + element.atomic-weight = override-atomic-weight + } + if override-common-name != none { + element.common-name = override-common-name + } + if override-iupac-name != none { + element.iupac-name = override-iupac-name + } + if override-cas != none { + element.override-cas = override-cas + } + if override-common-name != none { + element.common-name = override-common-name + } + if override-common-name != none { + element.common-name = override-common-name + } + if override-common-name != none { + element.common-name = override-common-name + } + return element +} + +#let get-shell-configuration(element) = { + element = get-element-dict(element) + let charge = element.at("charge", default: 0) + let electron-amount = element.atomic-number - charge + + let result = () + for value in shell-capacities { + if electron-amount <= 0 { + break + } + + if electron-amount >= value.at(1) { + result.push(value) + electron-amount -= value.at(1) + } else { + result.push((value.at(0), electron-amount)) + electron-amount = 0 + } + } + return result +} + +//TODO: fix Cr and Mo +#let get-electron-configuration(element) = { + element = get-element-dict(element) + let charge = element.at("charge", default: 0) + let electron-amount = element.atomic-number - charge + + let result = () + for value in orbital-capacities { + if electron-amount <= 0 { + break + } + if electron-amount >= value.at(1) { + result.push(value) + electron-amount -= value.at(1) + } else { + result.push((value.at(0), electron-amount)) + electron-amount = 0 + } + } + return result +} + +#let display-electron-configuration(element, short: false) = { + let configuration = get-electron-configuration(element) + if short { + let prefix = "" + if configuration.at(14, default: (0, 0)).at(1) == 6 { + configuration = configuration.slice(15) + prefix = "[Rn]" + } else if configuration.at(10, default: (0, 0)).at(1) == 6 { + configuration = configuration.slice(11) + prefix = "[Xe]" + } else if configuration.at(7, default: (0, 0)).at(1) == 6 { + configuration = configuration.slice(8) + prefix = "[Kr]" + } else if configuration.at(4, default: (0, 0)).at(1) == 6 { + configuration = configuration.slice(5) + prefix = "[Ar]" + } else if configuration.at(2, default: (0, 0)).at(1) == 6 { + configuration = configuration.slice(3) + prefix = "[Ne]" + } else if configuration.at(0, default: (0, 0)).at(1) == 2 { + configuration = configuration.slice(1) + prefix = "[He]" + } + + return prefix + configuration.map(x => $#x.at(0)^#str(x.at(1))$).sum() + } else { + return configuration.map(x => $#x.at(0)^#str(x.at(1))$).sum() + } +} \ No newline at end of file diff --git a/src/model/group-element.typ b/src/model/group-element.typ new file mode 100644 index 0000000..81e72b4 --- /dev/null +++ b/src/model/group-element.typ @@ -0,0 +1,60 @@ +#import "@preview/elembic:1.1.0" as e +#import "../utils.typ": ( + count-to-content, + charge-to-content, + get-bracket, + customizable-attach, +) + +#let group( + kind: 1, + count: 1, + charge: 0, + grow-brackets: true, + affect-layout: true, + ..children, +) = { } + +#let draw-group(it) = { + let result = customizable-attach( + if it.grow-brackets { + math.lr({ + get-bracket(it.kind, open: true) + for child in it.children { + child + } + get-bracket(it.kind, open: false) + }) + } else { + get-bracket(it.kind, open: true) + for child in it.children { + child + } + get-bracket(it.kind, open: false) + }, + tr: charge-to-content(it.charge), + br: count-to-content(it.count), + affect-layout: it.affect-layout, + ) + + return result +} +} + + +#let group = e.element.declare( + "group", + prefix: "@preview/typsium:0.3.0", + + display: draw-group, + + fields: ( + e.field("children", e.types.array(content), required: true), + e.field("kind", int, default: 0), + e.field("count", e.types.union(int, content), default: 1), + e.field("charge", e.types.union(int, content), default: 0), + e.field("grow-brackets", bool, default: false), + e.field("affect-layout", bool, default: true), + ), +) + diff --git a/src/model/molecule-element.typ b/src/model/molecule-element.typ new file mode 100644 index 0000000..54fc9c6 --- /dev/null +++ b/src/model/molecule-element.typ @@ -0,0 +1,52 @@ +#import "@preview/elembic:1.1.0" as e +#import "../utils.typ": ( + count-to-content, + charge-to-content, + is-default, + customizable-attach, + phase-to-content, +) + +#let molecule( + count: 1, + phase: none, + charge: 0, + //TODO: add up and down arrows + phase-transition: 0, + affect-layout: true, + ..children, +) = { } + +#let draw-molecule(it) = { + let result = count-to-content(it.count) + for child in it.children { + result += child + } + if not is-default(it.charge) { + result = customizable-attach( + result, + tr: charge-to-content(it.charge), + affect-layout: it.affect-layout, + ) + } + if not is-default(it.phase) { + result += context { + text(phase-to-content(it.phase), size: text.size * 0.75) + } + } + return result +} + +#let molecule = e.element.declare( + "molecule", + prefix: "@preview/typsium:0.3.0", + + display: draw-molecule, + fields: ( + e.field("children", e.types.array(content), required: true), + e.field("count", e.types.union(int, content), default: 1), + e.field("phase", e.types.union(str, content), default: none), + e.field("charge", e.types.union(int, content), default: 0), + e.field("affect-layout", bool, default: true), + ), +) diff --git a/src/model/molecule-variable.typ b/src/model/molecule-variable.typ new file mode 100644 index 0000000..f85e247 --- /dev/null +++ b/src/model/molecule-variable.typ @@ -0,0 +1,118 @@ +#import "@preview/elembic:1.1.0" as e +#import "../utils.typ": ( + // is-sequence, + // is-kind, + // is-heading, + // is-metadata, + // padright, + // get-all-children, + // hydrates, + // elements, + // get-element-dict, + // get-molecule-dict, + // to-string, +) + +#let element-variable = e.types.declare( + "molecule-variable", + prefix: "@preview/typsium:0.3.0", + fields: () +) + +#let get-weight(molecule) = { + let element = get-element-dict(molecule) + molecule = get-molecule-dict(molecule) + if type(element) == dictionary and element.at("atomic-weight", default: none) != none { + return element.atomic-weight + } + let weight = 0 + for value in molecule.elements { + let element = elements.find(x => x.symbol == value.at(0)) + + weight += element.atomic-weight * value.at(1) + } + return weight +} + +#let define-molecule( + common-name: none, + iupac-name: none, + formula: "", + smiles: "", + inchi: "", + cas: "", + h-p: (), + ghs: (), + validate: true, +) = { + let found-elements + if validate { + // TODO: continue to add more validation as we go + // things should fail here instead of causing errors down the line + if common-name == none { + common-name = formula + } + + if smiles == "" { + smiles == none + } else if formula == "" { + //TODO: actually calculate the formula based on the smiles code (don't forget to add H on Carbon atoms) + formula = smiles + } + + if cas == "" { + cas = none + } + + found-elements = get-element-counts(formula) + + if inchi != "" { + // TODO: create InChI keys from provided InChI: + // https://typst.app/universe/package/jumble + // https://www.inchi-trust.org/download/104/InChI_TechMan.pdf + } else { + inchi = none + } + } + + return metadata(( + kind: "molecule", + common-name: common-name, + iupac-name: iupac-name, + formula: formula, + smiles: smiles, + inchi: inchi, + cas: cas, + h-p: h-p, + ghs: ghs, + elements: found-elements, + )) +} + +#let define-hydrate( + molecule, + amount: 1, + override-common-name: none, + override-iupac-name: none, + override-smiles: none, + override-inchi: none, + override-cas: none, + override-h-p: none, + override-ghs: none, +) = { + molecule = get-molecule-dict(molecule) + define-molecule( + common-name: if override-common-name != none { override-common-name } else { + molecule.common-name + sym.space + hydrates.at(amount) + }, + iupac-name: if override-iupac-name != none { override-iupac-name } else { + molecule.iupac-name + sym.semi + hydrates.at(amount) + }, + formula: molecule.formula + sym.space.narrow + sym.dot + sym.space.narrow + str(amount) + "H2O", + smiles: if override-smiles != none { override-smiles } else { molecule.smiles }, + inchi: if override-inchi != none { override-inchi } else { molecule.inchi }, + cas: if override-cas != none { override-cas } else { molecule.cas }, + h-p: if override-h-p != none { override-h-p } else { molecule.h-p }, + ghs: if override-ghs != none { override-ghs } else { molecule.ghs }, + ) +} \ No newline at end of file diff --git a/src/model/reaction-element.typ b/src/model/reaction-element.typ new file mode 100644 index 0000000..63ab1a1 --- /dev/null +++ b/src/model/reaction-element.typ @@ -0,0 +1,59 @@ + +#import "@preview/elembic:1.1.0" as e +#import "../utils.typ": get-arrow, is-default + +#let reaction( + children: (), +) = { } + +#let draw-reaction(it) = { + for child in it.children { + if child == [+] { + h(0.4em, weak: true) + math.plus + h(0.4em, weak: true) + } else { + let type-id = e.data(child).eid + if type-id == "e_typsium_---_arrow" { + h(0.4em, weak: true) + child + h(0.4em, weak: true) + } else if type-id == "e_typsium_---_molecule" { + child + let last = e.data(e.data(child).fields.children.last()) + let last-child-type-id = last.eid + let charge = last.fields.at("charge", default: none) + let count = last.fields.at("count", default: none) + if ( + last-child-type-id == "e_typsium_---_group" + and (not is-default(charge) or (not is-default(count) and count != 1)) + ) { + h(-0.4em) + } + } + // else if type-id == "e_typsium_---_group"{ + // child + // let charge = last.fields.at("charge", default: none) + // let count = last.fields.at("count", default: none) + // if not is-default(charge) or (not is-default(count) and count != 1){ + // h(-0.3em) + // } + // } + else { + child + } + } + } +} +} + +#let reaction = e.element.declare( + "reaction", + prefix: "@preview/typsium:0.3.0", + + display: draw-reaction, + + fields: ( + e.field("children", e.types.array(content), required: true), + ), +) diff --git a/src/parse-content-intermediate-representation.typ b/src/parse-content-intermediate-representation.typ new file mode 100644 index 0000000..4594915 --- /dev/null +++ b/src/parse-content-intermediate-representation.typ @@ -0,0 +1,517 @@ +#import "utils.typ": ( + get-all-children, + is-metadata, + typst-builtin-styled, + typst-builtin-context, + length, + reconstruct-content-from-strings, + reconstruct-nested-content, + is-kind, + arrow-string-to-kind, + is-default, + roman-to-number, +) +#import "parse-formula-intermediate-representation.typ": patterns + +#import "model/molecule-element.typ": molecule +#import "model/reaction-element.typ": reaction +#import "model/element-element.typ": element +#import "model/group-element.typ": group +#import "model/arrow-element.typ": arrow + +#let get-count-and-charge(count1, count2, charge1, charge2, full-string, templates, index) = { + let radical = false + let roman-charge = false + let count = if not is-default(count1) { + reconstruct-content-from-strings( + full-string, + templates, + start: index + if count1.contains("_") { 1 }, + end: index + length(count1), + ) + // templates.slice() + } else if not is-default(count2) { + reconstruct-content-from-strings( + full-string, + templates, + start: index + length(charge1) + if count2.contains("_") { 1 }, + end: index + length(charge1) + length(count2), + ) + } else { + none + } + + let charge = if not is-default(charge1) { + reconstruct-content-from-strings( + full-string, + templates, + start: index + if charge1.contains("^") { 1 }, + end: index + length(charge1), + ) + } else if not is-default(charge2) { + reconstruct-content-from-strings( + full-string, + templates, + start: index + length(count1) + if charge2.contains("^") { 1 }, + end: index + length(count1) + length(charge2), + ) + } else { + none + } + return (count, charge, radical, roman-charge) +} + +#let string-to-element(formula, full-string, templates, index) = { + let element-match = formula.match(patterns.element) + if element-match == none { + return (false,) + } + let symbol = element-match.captures.at(0) + let oxidation = element-match.captures.at(5) + let x = get-count-and-charge( + element-match.captures.at(1), + element-match.captures.at(3), + element-match.captures.at(2), + element-match.captures.at(4), + full-string, + templates, + index + symbol.len(), + ) + let oxidation-number = none + let roman-oxidation = true + let roman-charge = false + if oxidation != none { + oxidation = upper(oxidation) + oxidation = oxidation.replace("^", "", count: 2) + let multiplier = if oxidation.contains("-") { -1 } else { 1 } + oxidation = oxidation.replace("-", "").replace("+", "") + if oxidation.contains("I") or oxidation.contains("V") { + oxidation-number = roman-to-number(oxidation) + } else { + roman-oxidation = false + oxidation-number = int(oxidation) + } + if oxidation-number != none { + oxidation-number *= multiplier + } + } + + if x.at(0) == none and x.at(1) == none and x.at(2) == false { + if formula.at(element-match.end, default: "").match(regex("[a-z]")) != none { + return (false,) + } + } + + return ( + true, + element( + reconstruct-content-from-strings( + full-string, + templates, + start: index, + end: index + element-match.captures.at(0).len(), + ), + count: x.at(0), + charge: x.at(1), + radical: x.at(2), + oxidation: oxidation-number, + roman-oxidation: roman-oxidation, + roman-charge: x.at(3), + ), + element-match.end, + ) +} + +#let string-to-math(formula) = { + let match = formula.match(patterns.math) + if match == none { + return (false,) + } + return (true, eval(match.text), match.end) +} + +#let string-to-reaction( + reaction-string, + templates, + create-molecules: true, +) = { + let remaining = reaction-string + if remaining.len() == 0 { + return () + } + let full-reaction = () + let current-molecule-children = () + let current-molecule-count = 1 + let current-molecule-phase = none + let current-molecule-charge = 0 + let random-content = 0 + + let index = 0 + while remaining.len() > 0 { + if remaining.at(0) == "&" { + //flush current molecule + if current-molecule-children.len() > 0 { + full-reaction.push(molecule(current-molecule-children)) + current-molecule-children = () + } + //end flush current molecule + + full-reaction.push($&$) + remaining = remaining.slice(1) + index += 1 + continue + } + + let math-result = string-to-math(remaining) + if math-result.at(0) { + //flush random content + if random-content != 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + random-content = 0 + } + //end flush random content + + full-reaction.push(math-result.at(1)) + remaining = remaining.slice(math-result.at(2)) + index += math-result.at(2) + continue + } + + let element = string-to-element(remaining, reaction-string, templates, index) + if element.at(0) { + //flush random content + if random-content != 0 { + if current-molecule-children.len() == 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + } else { + current-molecule-children.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + } + random-content = 0 + } + //end flush random content + current-molecule-children.push(element.at(1)) + remaining = remaining.slice(element.at(2)) + index += element.at(2) + continue + } + + + let group-match = remaining.match(patterns.group) + if group-match != none { + //flush random content + if random-content != 0 { + if current-molecule-children.len() == 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + } else { + current-molecule-children.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + } + random-content = 0 + } + //end flush random content + + let group-content = group-match.captures.at(0) + let kind = if group-content.at(0) == "(" { + group-content = group-content.trim(regex("[()]"), repeat: false) + 0 + } else if group-content.at(0) == "[" { + group-content = group-content.trim(regex("[\[\]]"), repeat: false) + 1 + } else if group-content.at(0) == "{" { + group-content = group-content.trim(regex("[{}]"), repeat: false) + 2 + } + let x = get-count-and-charge( + group-match.captures.at(1), + group-match.captures.at(3), + group-match.captures.at(2), + group-match.captures.at(4), + ) + let group-children = string-to-reaction(group-content, create-molecules: false) + + current-molecule-children.push(group(group-children, kind: kind, count: x.at(0), charge: x.at(1))) + remaining = remaining.slice(group-match.end) + index += group-match.end + continue + } + + let plus-match = remaining.match(patterns.reaction-plus) + if plus-match != none { + //flush current molecule + if current-molecule-children.len() > 0 { + full-reaction.push( + molecule( + current-molecule-children, + count: current-molecule-count, + phase: current-molecule-phase, + charge: current-molecule-charge, + ), + ) + current-molecule-children = () + } + //end flush current molecule + + //flush random content + if random-content != 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + random-content = 0 + } + //end flush random content + + full-reaction.push([+]) + remaining = remaining.slice(plus-match.end) + index += plus-match.end + continue + } + + let arrow-match = remaining.match(patterns.reaction-arrow) + if arrow-match != none { + //flush current molecule + if current-molecule-children.len() > 0 { + full-reaction.push( + molecule( + current-molecule-children, + count: current-molecule-count, + phase: current-molecule-phase, + charge: current-molecule-charge, + ), + ) + current-molecule-children = () + } + //end flush current molecule + + //flush random content + if random-content != 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + random-content = 0 + } + //end flush random content + + let kind = arrow-string-to-kind(arrow-match.captures.at(0)) + let top = () + let bottom = () + if arrow-match.captures.at(1) != none { + top = string-to-reaction( + arrow-match.captures.at(1), + templates.slice( + index + arrow-match.captures.at(0).len() + 2, + count: arrow-match.captures.at(1).len() + 2, + ), + ) + } + if arrow-match.captures.at(2) != none { + bottom = string-to-reaction( + arrow-match.captures.at(2), + templates.slice( + index + arrow-match.captures.at(0).len() + length(arrow-match.captures.at(1)) + 2 + 2, + count: arrow-match.captures.at(2).len() + 2, + ), + ) + } + full-reaction.push(arrow(kind: kind, top: top, bottom: bottom)) + remaining = remaining.slice(arrow-match.end) + index += arrow-match.end + continue + } + let current-character = remaining.codepoints().at(0) + if (current-character == "#" and templates.at(index).len() != 0) { + //flush current molecule + if current-molecule-children.len() > 0 { + full-reaction.push( + molecule( + current-molecule-children, + count: current-molecule-count, + phase: current-molecule-phase, + charge: current-molecule-charge, + ), + ) + current-molecule-children = () + } + //end flush current molecule + + //flush random content + if random-content != 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: index - random-content, + end: index, + ), + ) + random-content = 0 + } + //end flush random content + + full-reaction.push(reconstruct-nested-content(templates.at(index).slice(1), templates.at(index).at(0))) + } else { + random-content += current-character.len() + } + + remaining = remaining.slice(current-character.len()) + index += current-character.len() + } + + //flush current molecule + if current-molecule-children.len() != 0 { + full-reaction.push( + molecule(current-molecule-children, count: current-molecule-count, phase: current-molecule-phase), + ) + } + //flush random content + if random-content != 0 { + full-reaction.push( + reconstruct-content-from-strings( + reaction-string, + templates, + start: reaction-string.len() - random-content, + end: reaction-string.len(), + ), + ) + } + + return full-reaction +} + +#let create-full-string(children) = { + let full-string = "" + let templates = () + for child in children { + if is-metadata(child) { + if is-kind(child, "molecule") { + full-string += child.value.formula + for value in child.value.formula { + templates.push(()) + } + } else if is-kind(child, "element") { + full-string += child.value.symbol + for value in child.value.symbol { + templates.push(()) + } + } + } else if type(child) == content { + let func-type = child.func() + if child == [ ] { + full-string += " " + templates.push(()) + } else if func-type == text { + full-string += child.text + for value in child.text { + templates.push(()) + } + } else if func-type == typst-builtin-styled { + let (inner-full-strings, inner-templates) = create-full-string(get-all-children(child.child)) + for value in range(inner-templates.len()) { + inner-templates.at(value).push(child) + } + full-string += inner-full-strings + templates += inner-templates + } else if ( + func-type + in ( + math.overbrace, + math.underbrace, + math.underbracket, + math.overbracket, + math.underparen, + math.overparen, + math.undershell, + math.overshell, + pad, + strong, + highlight, + overline, + underline, + strike, + math.cancel, + //TODO: implement missing methods in utils: + figure, + quote, + emph, + smallcaps, + sub, + super, + box, + block, + hide, + move, + scale, + circle, + ellipse, + rect, + square, + ) + ) { + let (inner-full-strings, inner-templates) = create-full-string(get-all-children(child.body)) + for value in range(inner-templates.len()) { + inner-templates.at(value).push(child) + } + full-string += inner-full-strings + templates += inner-templates + } else { + full-string += "#" + templates.push((child,)) + continue + } + } + } + return (full-string, templates) +} + +#let content-to-reaction(body) = { + if type(body) != content { + return () + } + let children = get-all-children(body) + let (full-string, templates) = create-full-string(children) + + return string-to-reaction(full-string, templates) +} diff --git a/src/parse-formula-intermediate-representation.typ b/src/parse-formula-intermediate-representation.typ index 8a71f49..b7fcb54 100644 --- a/src/parse-formula-intermediate-representation.typ +++ b/src/parse-formula-intermediate-representation.typ @@ -1,40 +1,63 @@ -#import "utils.typ": arrow-string-to-kind +#import "utils.typ": arrow-string-to-kind, is-default, roman-to-number +#import "model/molecule-element.typ": molecule +#import "model/reaction-element.typ": reaction +#import "model/element-element.typ": element +#import "model/group-element.typ": group +#import "model/arrow-element.typ": arrow + #let patterns = ( - // element: regex("^(?P[A-Z][a-z]?)(?:(?P_?\d+)|(?P\^?[+-]?\d*\.?-?))?(?:(?P_?\d+)|(?P\^?[+-]?\d*\.?-?))?"), - element: regex("^(?P[A-Z][a-z]?)(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?"), - // group: regex("^(\((?:[^()]|(?R))*\)|\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\])"), - group: regex("^(?P\((?:[^()]|(?R))*\)|\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\])(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?"), - reaction-plus: regex("^(\s?\+\s?)"), - reaction-arrow: regex("^\s?(<->|<=>|->|<-|=>|<=|-\/>|<\/-)(?:\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\])?(?:\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\])?\s?"), - math: regex("^(\$[^$]*\$)"), - // Match physical states (s/l/g/aq) - state: regex("^\((s|l|g|aq|solid|liquid|gas|aqueous)\)"), + element: regex( + "^([A-Z][a-z]?)" + + "(?:(_?\d+)|(\^\.?[+-]?\d+[+-]?|\^\.?[+-.]{1}|\^?[+-]?[IV]+|\.?[+-]{1}\d?))?" + + "(?:(_?\d+)|(\^\.?[+-]?\d+[+-]?|\^\.?[+-.]{1}|\^?[+-]?[IV]+|\.?[+-]{1}\d?))?" + + "(\^\^[+-]?(?:[IViv]{1,3}|\d+))?" + ), + group: regex( + "^((?:\([^()]*(?:\([^()]*\)[^()]*)*\))|(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\})|(?:\[[^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*\]))" + + "(?:(_?\d+)|(\^\.?[+-]?\d+[+-]?|\^\.?[+-.]{1}|[+-]{1}\d?))?" + + "(?:(_?\d+)|(\^\.?[+-]?\d+[+-]?|\^\.?[+-.]{1}|[+-]{1}\d?))?" + ), + reaction-plus: regex("^\s*\+\s*"), + reaction-arrow: regex( + "^\s*(<->|↔|<=>|⇔|->|→|<-|←|=>|⇒|<=|⇐|-/?\>| 0 { - if remaining.at(0) == "&" { - group.children.push((type: "align")) - remaining = remaining.slice(1) - continue - } - let math-result = string-to-math(remaining) - if math-result.at(0) { - if random-content != none and random-content != "" { - group.children.push((type: "content", body: [#random-content])) - } - random-content = "" - group.children.push(math-result.at(1)) - remaining = remaining.slice(math-result.at(2)) - continue - } - - let element = string-to-element(remaining) - if element.at(0) { - if random-content != none and random-content != "" { - group.children.push((type: "content", body: [#random-content])) - } - random-content = "" - group.children.push(element.at(1)) - remaining = remaining.slice(element.at(2)) - continue - } - - let result = string-to-group(remaining) - if result.at(0) { - if random-content != none and random-content != "" { - group.children.push((type: "content", body: [#random-content])) - } - random-content = "" - group.children.push(result.at(1)) - remaining = remaining.slice(result.at(2)) - continue - } - - random-content += remaining.codepoints().at(0) - remaining = remaining.slice(remaining.codepoints().at(0).len()) + let x = get-count-and-charge( + element-match.captures.at(1), + element-match.captures.at(3), + element-match.captures.at(2), + element-match.captures.at(4), + ) + let oxidation = element-match.captures.at(5) + let oxidation-number = none + let roman-oxidation = true + let roman-charge = false + if oxidation != none { + oxidation = upper(oxidation) + oxidation = oxidation.replace("^", "", count: 2) + let multiplier = if oxidation.contains("-") { -1 } else { 1 } + oxidation = oxidation.replace("-", "").replace("+", "") + if oxidation.contains("I") or oxidation.contains("V") { + oxidation-number = roman-to-number(oxidation) + } else { + roman-oxidation = false + oxidation-number = int(oxidation) } - - if random-content != none and random-content != "" { - group.children.push((type: "content", body: [#random-content])) + if oxidation-number != none { + oxidation-number *= multiplier } - return (true, group, group-match.end) } - return (false,) -} - -//this will assume that the string is a molecule for performance reasons -#let molecule-string-to-ir(formula) = { - let remaining = formula.trim() - if remaining.len() == 0 { - return none + if x.at(0) == none and x.at(1) == none and x.at(2) == false { + if formula.at(element-match.end, default: "").match(regex("[a-z]")) != none { + return (false,) + } } - let molecule = ( - type: "molecule", - children: (), + return ( + true, + if x.at(3) { + element( + element-match.captures.at(0), + count: x.at(0), + charge: x.at(1), + radical: x.at(2), + oxidation: oxidation-number, + roman-charge: true, + ) + } else { + element( + element-match.captures.at(0), + count: x.at(0), + charge: x.at(1), + radical: x.at(2), + oxidation: oxidation-number, + ) + }, + element-match.end, ) +} - let random-content = "" - - while remaining.len() > 0 { - if remaining.at(0) == "&" { - molecule.children.push((type: "align")) - remaining = remaining.slice(1) - continue - } - - let math-result = string-to-math(remaining) - if math-result.at(0) { - if random-content != none and random-content != "" { - molecule.children.push((type: "content", body: [#random-content])) - } - random-content = "" - molecule.children.push(math-result.at(1)) - remaining = remaining.slice(math-result.at(2)) - continue - } - - let element = string-to-element(remaining) - - if element.at(0) { - if random-content != none and random-content != "" { - molecule.children.push((type: "content", body: [#random-content])) - } - random-content = "" - molecule.children.push(element.at(1)) - remaining = remaining.slice(element.at(2)) - continue - } - - let group = string-to-group(remaining) - if group.at(0) { - if random-content != none and random-content != "" { - molecule.children.push((type: "content", body: [#random-content])) - } - random-content = "" - molecule.children.push(group.at(1)) - remaining = remaining.slice(group.at(2)) - continue - } - - random-content += remaining.codepoints().at(0) - remaining = remaining.slice(remaining.codepoints().at(0).len()) - } - - if random-content != none and random-content != "" { - molecule.children.push((type: "content", body: [#random-content])) +#let string-to-math(formula) = { + let match = formula.match(patterns.math) + if match == none { + return (false,) } - return molecule + return (true, eval(match.text), match.end) } -#let string-to-ir(reaction) = { - let remaining = reaction.trim() +#let string-to-reaction( + reaction-string, + create-molecules: true, +) = { + let remaining = reaction-string.trim() if remaining.len() == 0 { - return none + return () } let full-reaction = () - - let current-molecule = ( - type: "molecule", - children: (), - ) - + let current-molecule-children = () + let current-molecule-count = "" + let current-molecule-phase = none + let current-molecule-charge = 0 let random-content = "" + while remaining.len() > 0 { if remaining.at(0) == "&" { - if current-molecule.children.len() > 0 { - full-reaction.push(current-molecule) - current-molecule = (type: "molecule", children: ()) + if current-molecule-children.len() > 0 { + full-reaction.push(molecule(current-molecule-children)) + current-molecule-children = () } - full-reaction.push((type: "align")) + full-reaction.push($&$) remaining = remaining.slice(1) continue } let math-result = string-to-math(remaining) if math-result.at(0) { - if random-content != none and random-content != "" { - full-reaction.push((type: "content", body: [#random-content])) + if not is-default(random-content) { + full-reaction.push([#random-content]) } random-content = "" full-reaction.push(math-result.at(1)) @@ -281,83 +180,124 @@ let element = string-to-element(remaining) if element.at(0) { - if random-content != none and random-content != "" { - if current-molecule.children.len() == 0 { - full-reaction.push((type: "content", body: [#random-content])) + if not is-default(random-content) { + if current-molecule-children.len() == 0 { + full-reaction.push([#random-content]) } else { - current-molecule.children.push((type: "content", body: [#random-content])) + current-molecule-children.push([#random-content]) } } random-content = "" - current-molecule.children.push(element.at(1)) + current-molecule-children.push(element.at(1)) remaining = remaining.slice(element.at(2)) continue } - let group = string-to-group(remaining) - if group.at(0) { - if random-content != none and random-content != "" { - if current-molecule.children.len() == 0 { - full-reaction.push((type: "content", body: [#random-content])) + + let group-match = remaining.match(patterns.group) + if group-match != none { + if not is-default(random-content) { + if current-molecule-children.len() == 0 { + full-reaction.push([#random-content]) } else { - current-molecule.children.push((type: "content", body: [#random-content])) + current-molecule-children.push([#random-content]) } } random-content = "" - current-molecule.children.push(group.at(1)) - remaining = remaining.slice(group.at(2)) + + let group-content = group-match.captures.at(0) + let kind = if group-content.at(0) == "(" { + group-content = group-content.trim(regex("[()]"), repeat: false) + 0 + } else if group-content.at(0) == "[" { + group-content = group-content.trim(regex("[\[\]]"), repeat: false) + 1 + } else if group-content.at(0) == "{" { + group-content = group-content.trim(regex("[{}]"), repeat: false) + 2 + } + let x = get-count-and-charge( + group-match.captures.at(1), + group-match.captures.at(3), + group-match.captures.at(2), + group-match.captures.at(4), + ) + let group-children = string-to-reaction(group-content, create-molecules: false) + + current-molecule-children.push(group(group-children, kind: kind, count: x.at(0), charge: x.at(1))) + remaining = remaining.slice(group-match.end) continue } let plus-match = remaining.match(patterns.reaction-plus) if plus-match != none { - if current-molecule.children.len() > 0 { - full-reaction.push(current-molecule) - current-molecule = (type: "molecule", children: ()) + if current-molecule-children.len() > 0 { + full-reaction.push( + molecule( + current-molecule-children, + count: current-molecule-count, + phase: current-molecule-phase, + charge: current-molecule-charge, + ), + ) + current-molecule-children = () } - if random-content != none and random-content != "" { - full-reaction.push((type: "content", body: [#random-content])) + if not is-default(random-content) { + full-reaction.push([#random-content]) } random-content = "" - full-reaction.push((type: "+")) + full-reaction.push([+]) remaining = remaining.slice(plus-match.end) continue } let arrow-match = remaining.match(patterns.reaction-arrow) if arrow-match != none { - if current-molecule.children.len() > 0 { - full-reaction.push(current-molecule) - current-molecule = (type: "molecule", children: ()) + if current-molecule-children.len() > 0 { + full-reaction.push( + molecule( + current-molecule-children, + count: current-molecule-count, + phase: current-molecule-phase, + charge: current-molecule-charge, + ), + ) + current-molecule-children = () } - if random-content != none and random-content != "" { - full-reaction.push((type: "content", body: [#random-content])) + if not is-default(random-content) { + full-reaction.push([#random-content]) } random-content = "" - let arrow = ( - type: "arrow", - kind: arrow-string-to-kind(arrow-match.captures.at(0)), - ) + let kind = arrow-string-to-kind(arrow-match.captures.at(0)) + let top = () + let bottom = () if arrow-match.captures.at(1) != none { - arrow.top = string-to-ir(arrow-match.captures.at(1)) + top = string-to-reaction(arrow-match.captures.at(1)) } if arrow-match.captures.at(2) != none { - arrow.bottom = string-to-ir(arrow-match.captures.at(2)) + bottom = string-to-reaction(arrow-match.captures.at(2)) } - full-reaction.push(arrow) + full-reaction.push(arrow(kind: kind, top: top, bottom: bottom)) remaining = remaining.slice(arrow-match.end) continue } random-content += remaining.codepoints().at(0) - remaining = remaining.slice(remaining.codepoints().at(0).len()) - } - if current-molecule.children.len() != 0 { - full-reaction.push(current-molecule) + remaining = remaining.slice(remaining.codepoints().at(0).len()) } + if current-molecule-children.len() != 0 { + full-reaction.push( + molecule( + current-molecule-children, + count: current-molecule-count, + phase: current-molecule-phase, + charge: current-molecule-charge, + ), + ) } - if random-content != none and random-content != "" { - full-reaction.push((type: "content", body: [#random-content])) + if not is-default(random-content) { + full-reaction.push([#random-content]) } + return full-reaction } diff --git a/src/regex.typ b/src/regex.typ deleted file mode 100644 index 7758695..0000000 --- a/src/regex.typ +++ /dev/null @@ -1,45 +0,0 @@ -#let patterns = ( - // Match chemical elements with optional numbers (e.g., H2, Na, Fe3) - element: regex("^\s*?([A-Z][a-z]?)\s??(\d+(?:|[^\+|\-])*)?"), - - // Match brackets [] {} () with optional subscripts - bracket: regex("^\s*([\(\[\{\}\]\)])\s*?(\d+)?"), - - // Match ion charges (e.g., 2+, 3-, +) - charge: regex("^\s?\(?(\^?[0-9]?(\+|\-)|\^[0-9])\)?"), - - // Match physical states (s/l/g/aq) - state: regex("^\((s|l|g|aq|solid|liquid|gas|aqueous)\)"), - - // Match various types of reaction arrows with optional conditions in brackets - arrow: regex("^\s*(?:(<->|<==?>|-->|->|=|⇌|⇒|⇔)(?:\[([^\]]+)\])?|\[\])"), - - // Match plus signs between reactants/products - plus: regex("^\s\+\s?"), - - // Match heating conditions (Δ, heat, etc.) - heating: regex("^\s*(Δ|δ|Delta|delta|heat|fire|hot|heating)\s*"), - - // Match temperature specifications (e.g., T = 298K) - temperature: regex("^s*([Tt])\s*=\s*(\d+\.?\d*)\s*([K°C℃F])?"), - - // Match pressure specifications (e.g., P = 1atm) - pressure: regex("^\s*([Pp])\s*=\s*(\d+\.?\d*)\s*(atm|bar|Pa|kPa|mmHg)?"), - - // Match catalyst specifications - catalyst: regex("^\s*(cat|catalyst)\s*=?\s*([A-Za-z0-9\s]+)"), - - // Match general parameter assignments - parameter: regex("^\s*([A-Za-z0-9]+)\s*=?\s*([A-Za-z0-9\s]+)"), - - // Match commas separating conditions - comma: regex("^\s*,\s*"), - - // Match whitespace - whitespace: regex("^\s+"), - - // Match numerical values - number: regex("^\d+"), -) - -// === remove all non-regex related content === diff --git a/src/resources/arrow1.svg b/src/resources/arrow1.svg new file mode 100644 index 0000000..6e5adca --- /dev/null +++ b/src/resources/arrow1.svg @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/resources/arrow2.svg b/src/resources/arrow2.svg new file mode 100644 index 0000000..7a65867 --- /dev/null +++ b/src/resources/arrow2.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/typing.typ b/src/typing.typ new file mode 100644 index 0000000..3a6bc40 --- /dev/null +++ b/src/typing.typ @@ -0,0 +1,14 @@ +#import "@preview/elembic:1.1.0" as e: selector +#import "model/arrow-element.typ": arrow +#import "model/element-element.typ": element +#import "model/group-element.typ": group +#import "model/molecule-element.typ": molecule +#import "model/reaction-element.typ": reaction + +#let fields = e.fields +#let elembic = e +#let set-arrow = e.set_.with(arrow) +#let set-element = e.set_.with(element) +#let set-group = e.set_.with(group) +#let set-molecule = e.set_.with(molecule) +#let set-reaction = e.set_.with(reaction) diff --git a/src/utils.typ b/src/utils.typ index 4f695da..d8751a6 100644 --- a/src/utils.typ +++ b/src/utils.typ @@ -89,18 +89,41 @@ sym.arrow.l.not, sym.harpoons.rtlb, ) +#let roman-numerals = ( + "0", + "I", + "II", + "III", + "IV", + "V", + "VI", + "VII", + "VIII", + "IX", + "X", + "XI", + "XII", + "XIII", +) #let arrow-kinds = ( "<->": 0, + "↔": 0, "->": 1, + "→": 1, "<-": 2, + "←": 2, "=>": 3, - "<+": 4, + "⇒": 3, + "<=": 4, + "⇐": 4, "-/>": 5, "": 7, + "⇔": 7, ) #let get-bracket(kind, open: true) = { + kind = calc.clamp(kind, 0, 3) if not open { kind += 4 } @@ -118,42 +141,74 @@ } } -#let count-to-content(factor) = { - if factor == none { - none - } else if type(factor) == int { - if factor > 1 { - str(factor) +#let count-to-content(count) = { + if count == none { + return none + } else if type(count) == int { + if count > 1 { + return [#count] + } + } else if type(count) == content { + if count != [1] { + return count } } + return none } -#let arrow-string-to-kind(arrow) = { - arrow = arrow.trim() - arrow-kinds.at(arrow, default: 1) +#let roman-to-number(roman-number) = { + return roman-numerals.position(x => x == roman-number) } -#let charge-to-content(charge, radical: false) = { - if charge == none { - none - } else if type(charge) == int { - if radical { - sym.bullet +#let show-roman(body, roman: true) = { + if roman { + show "1": "I" + show "2": "II" + show "3": "III" + show "4": "IV" + show "5": "V" + show "6": "VI" + show "7": "VII" + show "8": "VIII" + body + } else { + show "V": "5" + show "I": "1" + show "II": "2" + show "III": "3" + show "IV": "4" + show "VI": "6" + show "VII": "7" + show "VIII": "8" // highest priority is lowest, otherwise it will render VII as 511 + body + } +} +#let oxidation-to-content( + oxidation, + roman: true, + negative-symbol: math.minus, + positive-symbol: math.plus, +) = { + if oxidation == none { + return none + } else if type(oxidation) == int { + let symbol = none + if oxidation < 0 { + symbol = negative-symbol + } else if oxidation > 0 { + symbol = positive-symbol } - if charge < 0 { - if calc.abs(charge) > 1 { - str(calc.abs(charge)) - } - math.minus - } else if charge > 0 { - if charge > 1 { - str(charge) - } - math.plus + if roman { + return [#symbol#roman-numerals.at(calc.abs(oxidation))] } else { - none + return [#symbol#calc.abs(oxidation)] } - } else if type(charge) == str { - charge.replace(".", sym.bullet).replace("-", math.minus).replace("+", math.plus) + } else if type(oxidation) == content { + return oxidation } + return none +} +#let arrow-string-to-kind(arrow) = { + arrow = arrow.trim() + arrow-kinds.at(arrow, default: 1) } #let parser-config = ( @@ -176,10 +231,22 @@ // https://github.com/touying-typ/touying/blob/6316aa90553f5d5d719150709aec1396e750da63/src/utils.typ#L157C1-L166C2 #let typst-builtin-sequence = ([A] + [ ] + [B]).func() +#let typst-builtin-styled = [#set text(fill: red)].func() +#let typst-builtin-context = [#context { }].func() +#let typst-builtin-space = [ ].func() + #let is-sequence(it) = { type(it) == content and it.func() == typst-builtin-sequence } +#let is-empty-content(it) = { + it in ([ ], parbreak(), linebreak()) +} + +#let is-styled(it) = { + type(it) == content and it.func() == typst-builtin-styled +} + #let is-metadata(it) = { type(it) == content and it.func() == metadata } @@ -196,6 +263,8 @@ type(it) == content and it.func() == heading and it.depth <= depth } + + // Following utility method is from: // https://github.com/typst-community/linguify/blob/b220a5993c7926b1d2edcc155cda00d2050da9ba/lib/utils.typ#L3 #let if-auto-then(val, ret) = { @@ -214,7 +283,51 @@ } } +#let none-coalesce(value, default) = { + if value == none { + return default + } else { + return value + } +} + // own utils +#let length(value) = { + if value == none { + return 0 + } + return value.len() +} +#let is-default(value) = { + if value == [] or value == none or value == auto or value == "" { + return true + } + return false +} + +#let customizable-attach( + base, + t: none, + tr: none, + br: none, + tl: none, + bl: none, + b: none, + affect-layout: true, +) = { + if affect-layout == false { + base = box(base) + } + math.attach( + base, + t: t, + tr: tr, + br: br, + tl: tl, + bl: bl, + b: b, + ) +} #let padright(array, targetLength) = { for value in range(array.len(), targetLength) { @@ -265,3 +378,170 @@ return element.value } } +#let reconstruct-content(template, body) = { + if template == none or template == auto { + return body + } + + let func = template.func() + + if func == typst-builtin-styled { + return template.func()(body, template.styles) + } else if func == typst-builtin-context { + template + } // else if func in (emph, smallcaps, sub, super, box, block, hide, heading) { + // return template.func()(body) + // } + else if ( + func + in ( + math.overbrace, + math.underbrace, + math.underbracket, + math.overbracket, + math.underparen, + math.overparen, + math.undershell, + math.overshell, + ) + ) { + return template.func()(body, template.at("annotation", default: none)) + } else if func == pad { + return template.func()( + body, + bottom: template.at("bottom", default: 0%), + top: template.at("top", default: 0%), + left: template.at("left", default: 0%), + right: template.at("right", default: 0%), + rest: template.at("rest", default: 0%), + ) + } else if func == strong { + return template.func()(body, delta: template.at("delta", default: 300)) + } else if func == highlight { + return template.func()( + body, + bottom-edge: template.at("bottom-edge", default: "descender"), + extent: template.at("extent", default: 0pt), + fill: template.at("fill", default: rgb("#fffd11a1")), + radius: template.at("radius", default: (:)), + stroke: template.at("stroke", default: (:)), + top-edge: template.at("top-edge", default: "ascender"), + ) + } else if func in (overline, underline, strike) { + return template.func()( + body, + background: template.at("background", default: false), + extent: template.at("extent", default: 0pt), + offset: template.at("offset", default: auto), + stroke: template.at("stroke", default: auto), + ) + } else if func == math.cancel { + return template.func()( + body, + angle: template.at("angle", default: auto), + cross: template.at("cross", default: false), + inverted: template.at("inverted", default: false), + length: template.at("length", default: 100% + 3pt), + stroke: template.at("stroke", default: 0.5pt), + ) + } else { + return template.func()(body) + } +} +#let reconstruct-nested-content(templates, body) = { + let result = body + for template in templates { + result = reconstruct-content(template, result) + } + return result +} +#let templates-equal(a, b) = { + if a.func() != b.func() { + return false + } + if a.func() == typst-builtin-styled { + return true + } + for i in a.fields() { + if i.at(0) != "child" and i.at(0) != "text" and i.at(0) != "body" { + if i.at(1) != b.at(i.at(0)) { + return false + } + } + } + return true +} +#let reconstruct-content-from-strings(strings, templates, start: 0, end: none) = { + if strings.len() == 1 { + return reconstruct-nested-content(templates.at(0), [#strings.at(0)]) + } + strings = strings.slice(start, end) + templates = templates.slice(start, end) + + let result = none + start = 0 + for i in range(1, templates.len()) { + let is-equal = templates.at(i).len() == templates.at(start).len() + if is-equal { + for j in range(0, templates.at(i).len()) { + if not templates-equal(templates.at(i).at(j), templates.at(start).at(j)) { + is-equal = false + } + } + } + if is-equal { + continue + } else { + result += reconstruct-nested-content(templates.at(start), [#strings.slice(start, i)]) + start = i + } + } + result += reconstruct-nested-content(templates.at(start), [#strings.slice(start, templates.len())]) + return result +} + +#let charge-to-content( + charge, + radical: false, + roman: false, + radical-symbol: sym.dot, + negative-symbol: math.minus, + positive-symbol: math.plus, +) = { + if is-default(charge) { + [] + } else if type(charge) == int { + if radical { + radical-symbol + } + if roman { + roman-numerals.at(calc.abs(charge)) + if charge < 0 { + negative-symbol + } else if charge > 0 { + positive-symbol + } + } else { + if charge < 0 { + if calc.abs(charge) > 1 { + str(calc.abs(charge)) + } + negative-symbol + } else if charge > 0 { + if charge > 1 { + str(charge) + } + positive-symbol + } else { + [] + } + } + } else if type(charge) == str { + charge.replace(".", radical-symbol).replace("-", negative-symbol).replace("+", positive-symbol) + } else if type(charge) == content { + show ".": radical-symbol + show "-": negative-symbol + show "+": positive-symbol + show-roman(charge, roman: roman) + } +} diff --git a/tests/0. Example book/main.png b/tests/0. Example book/main.png new file mode 100644 index 0000000..810bc38 Binary files /dev/null and b/tests/0. Example book/main.png differ diff --git a/tests/0. Example book/main.typ b/tests/0. Example book/main.typ new file mode 100644 index 0000000..ab61e0c --- /dev/null +++ b/tests/0. Example book/main.typ @@ -0,0 +1,114 @@ +#import "@preview/cram-snap:0.2.2": * +#import "../../src/lib.typ": * +//#import "@preview/typsium:0.3.0": * +#import "@preview/zebraw:0.5.5": * +#import "@preview/cuti:0.3.0":* + +#set page(margin: 0.8cm, height: auto) +#set text(size: 14pt) +#show raw: set text(font: "IBM Plex Sans", size: 11pt) +#show: set-group(grow-brackets:false, affect-layout:false) +#show: cram-snap.with( + title: [Example sheet], + subtitle: [ + #v(-1em) + For: 0.3.0\ + Written on: #datetime.today().display() + ], + column-number: 1, + fill-color: "d0e0d0", + stroke-color: "343434", +) + +#align(center)[ + #text(size: 24pt, weight: "bold", fill: rgb("2c5aa0"))[ + #show raw: set text(size: 24pt) + Basic Usage + ] + #v(-0.9em) + #line(length: 60%, stroke: 1pt + rgb("2c5aa0")) + #v(-0.3em) +] + +#table(inset: 0.7em)[ + Effect +][ + Grammar +][ + #ce[H2O] +][ + ```typ #ce[H2O]``` or ```typ #ce("H2O")``` +][ + #ce[H+] +][ + ```typ #ce[H^+]``` or ```typ #ce[H+]``` +][ + #ce[H-] +][ + ```typ #ce[H^-]``` or ```typ #ce[H-]``` +][ + #ce[O^2-] +][ + ```typ #ce[O^2-]``` +][ + #ce("[Fe(CN)6]^4+") +][ + ```typ #ce("[Fe(CN)6]^4+")``` +][ + #ce[CuSO4*5H2O] +][ + ```typ #ce[CuSO4*5H2O]``` +][ + #ce[->] +][ + ```typ #ce("->")``` or ```typ #ce[->]``` +][ + #ce("->[600°C][200atm]") +][ + ```typ #ce("->[600°C][200atm]")``` +][ + #ce[Cu-2^^2] +][ + ```typ #ce[Cu-2^^2]``` +][ + roman-charge\ + roman-oxidation +][ + ```typ #show: set-element(roman-charge: true)``` \ ```typ #show: set-element(roman-oxidation: true)``` +] +#align(center)[ + #text(size: 24pt, weight: "bold", fill: rgb("2c5aa0"))[ + #show raw: set text(size: 24pt) + Content in #raw("ce", lang: "typ") + ] + #v(-0.9em) + #line(length: 60%, stroke: 1pt + rgb("2c5aa0")) + #v(-0.3em) +] + +#table(inset: 0.7em)[ + effect +][ + content +][ + #ce[#text(red)[H2]] +][ + ```typ #ce[2#text(red)[H2]]``` +][ + $overbrace(#ce("H2O"),"water")$ +][ + ```typ $overbrace(#ce("H2O"),"water")$ ``` +][ + #ce[*H2O*] +][ + ```typ #ce[*H2O*]``` +][ + #ce[#fakeitalic[H2O]] +][ + ```typ + #import "@preview/cuti:0.3.0":* //or newer + #ce[#fakeitalic[H2O]] + + ``` +] +//... \ No newline at end of file diff --git a/tests/README-graphic1/ref/1.png b/tests/README-graphic1/ref/1.png index 88ec8da..f10a287 100644 Binary files a/tests/README-graphic1/ref/1.png and b/tests/README-graphic1/ref/1.png differ diff --git a/tests/README-graphic1/test.typ b/tests/README-graphic1/test.typ index 2c7e2a8..75536b7 100644 --- a/tests/README-graphic1/test.typ +++ b/tests/README-graphic1/test.typ @@ -1,4 +1,6 @@ -#import "../../src/lib.typ" : ce -#set page(width: auto, height: auto, margin: 0.5em) +#import "../../src/lib.typ": ce +#set page(width: auto, height: auto, margin: 0em) -$#ce("[Cu(H2O)4]^2+ + 4NH3 -> [Cu(NH3)4]^2+ + 4H2O")$ +$ + #ce("[Cu(H2O)4]^2+ + 4NH3 -> [Cu(NH3)4]^2+ + 4H2O") +$ diff --git a/tests/arrow-align/ref/1.png b/tests/arrow-align/ref/1.png index b63e8a0..69d1868 100644 Binary files a/tests/arrow-align/ref/1.png and b/tests/arrow-align/ref/1.png differ diff --git a/tests/arrow-align/test.svg b/tests/arrow-align/test.svg new file mode 100644 index 0000000..1201db9 --- /dev/null +++ b/tests/arrow-align/test.svg @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/arrow-align/test.typ b/tests/arrow-align/test.typ index 7c7bf5e..e64aa64 100644 --- a/tests/arrow-align/test.typ +++ b/tests/arrow-align/test.typ @@ -1,9 +1,7 @@ -#import "../../src/lib.typ" : ce -//#import "@preview/whalogen:0.3.0": ce - +#import "../../src/lib.typ": ce #set page(width: auto, height: auto, margin: 0.5em) $ -#ce("A &-> B")\ -#ce("AAAAAAAAAA &-> BBBB") -$ \ No newline at end of file + #ce("A &-> B")\ + #ce("AAAAAAAAAA &-> BBBB") +$ diff --git a/tests/brackets/.DS_Store b/tests/brackets/.DS_Store new file mode 100644 index 0000000..aed2c26 Binary files /dev/null and b/tests/brackets/.DS_Store differ diff --git a/tests/brackets/ref/1.png b/tests/brackets/ref/1.png index b6da07b..e6cc486 100644 Binary files a/tests/brackets/ref/1.png and b/tests/brackets/ref/1.png differ diff --git a/tests/brackets/test.typ b/tests/brackets/test.typ index 63825a2..01e6a67 100644 --- a/tests/brackets/test.typ +++ b/tests/brackets/test.typ @@ -1,9 +1,14 @@ -//#import "../../src/lib.typ" : ce -#import "@preview/whalogen:0.3.0": ce -//#import "@preview/typsium:0.2.0": ce +#import "../../src/lib.typ" : ce +#import "@preview/elembic:1.1.0" as e +#import "../../src/model/group-element.typ":* + +#show: e.set_(group, grow-brackets:true, affect-layout:true) #set page(width: auto, height: auto, margin: 0.5em) +// #ce("({[(Na2)2]2}2)")\ +// #ce("A + B =>[H2SO4][Hello World] C + D")\ +// #ce("2[Fe(CN)6]^4+") #linebreak() #ce("3[Co(NH3)4]^2+") @@ -11,3 +16,5 @@ #ce("[FeCo(CN)4 (NH3)2]^5-") #linebreak() #ce("[Co(en)3]^3- + 3[HCl]^+") +\ +#ce("[CH2[NH3]]2SO2") \ No newline at end of file diff --git a/tests/charges/ref/1.png b/tests/charges/ref/1.png index 3d34e73..298ea49 100644 Binary files a/tests/charges/ref/1.png and b/tests/charges/ref/1.png differ diff --git a/tests/charges/test.typ b/tests/charges/test.typ index 931f744..2b9da8c 100644 --- a/tests/charges/test.typ +++ b/tests/charges/test.typ @@ -1,16 +1,39 @@ -#import "../../src/lib.typ" : ce -//#import "@preview/whalogen:0.3.0": ce - +#import "../../src/lib.typ": ce #set page(width: auto, height: auto, margin: 0.5em) -#ce("Fe 3+") -#linebreak() -#ce("OH^1-") -#linebreak() -#ce("OH- + H+ -> H2O") -#linebreak() -#ce("OH-3") -#linebreak() -#ce("Fe(OH)2^0") -#linebreak() -#ce("PO4-3") + +#ce("H^-IV") +#ce("H^IV") +#ce("H^.2+") +#ce("H^+2") +#ce("H^3+") +#ce("H^.2") +#ce("H^+") +#ce("H+") +#ce("H+2") +#ce("H^.") +#ce("H+") + +#ce("H2^-IV") +#ce("H2^IV") +#ce("H2^.2+") +#ce("H2^+2") +#ce("H2^3+") +#ce("H2^.2") +#ce("H2^+") +#ce("H2+") +#ce("H2+2") +#ce("H2^.") +#ce("H2+") + +#ce("H2^-IV^^1") +#ce("H2^IV^^1") +#ce("H2^.2+^^1") +#ce("H2^+2^^1") +#ce("H2^3+^^1") +#ce("H2^.2^^1") +#ce("H2^+^^1") +#ce("H2+^^1") +#ce("H2+2^^1") +#ce("H2^.^^1") +#ce("H2+^^1") diff --git a/tests/intermediate-representation-reactions/.gitignore b/tests/content-to-reaction/.gitignore similarity index 100% rename from tests/intermediate-representation-reactions/.gitignore rename to tests/content-to-reaction/.gitignore diff --git a/tests/content-to-reaction/ref/1.png b/tests/content-to-reaction/ref/1.png new file mode 100644 index 0000000..9eee342 Binary files /dev/null and b/tests/content-to-reaction/ref/1.png differ diff --git a/tests/content-to-reaction/test.typ b/tests/content-to-reaction/test.typ new file mode 100644 index 0000000..076b567 --- /dev/null +++ b/tests/content-to-reaction/test.typ @@ -0,0 +1,64 @@ +#import "../../src/lib.typ" : ce, define-molecule, get-element +#import "../../src/utils.typ" : * +#import "@preview/elembic:1.1.0" as e +#import "../../src/model/group-element.typ":* +#import "../../src/model/element-element.typ":* +#import "../../src/parse-formula-intermediate-representation.typ": string-to-reaction, +#import "@preview/alchemist:0.1.4": * +// #show: e.set_(group, grow-brackets:false, affect-layout:false) + +#set page(width: auto, height: auto, margin: 0.5em) + +// #show: show-roman.with(roman: false) + +// #show: e.set_(element, affect-layout:true,roman-charge:false) + + +#let alchemist-molecule = skeletize({ + molecule(name: "A", "A") + single() + molecule("B") + branch({ + single(angle: 1) + molecule( + "W", + links: ( + "A": double(stroke: red), + ), + ) + single() + molecule(name: "X", "X") + }) + branch({ + single(angle: -1) + molecule("Y") + single() + molecule( + name: "Z", + "Z", + links: ( + "X": single(stroke: black + 3pt), + ), + ) + }) + single() + molecule( + "C", + links: ( + "X": cram-filled-left(fill: blue), + "Z": single(), + ), + ) +}) + +$ +#ce[H2SO4 ->H2O + #math.overbrace[#alchemist-molecule][Hello World]]\ +$ +#ce[#text(green)[He2]#math.cancel[S]O4^#text(blue)[#math.cancel[5]-]] + +#ce[A + B =>[PO4-3][Hello World] C + D]\ + +#let sulfuric-acid = define-molecule(formula: "H2SO4") +#let iron = get-element(symbol:"Fe") + +#ce[#sulfuric-acid + 2#iron] diff --git a/tests/parse-ir-elements/.gitignore b/tests/elembic/.gitignore similarity index 88% rename from tests/parse-ir-elements/.gitignore rename to tests/elembic/.gitignore index 03681f9..40223be 100644 --- a/tests/parse-ir-elements/.gitignore +++ b/tests/elembic/.gitignore @@ -2,4 +2,3 @@ diff/** out/** -ref/** diff --git a/tests/elembic/test.typ b/tests/elembic/test.typ new file mode 100644 index 0000000..178867f --- /dev/null +++ b/tests/elembic/test.typ @@ -0,0 +1,40 @@ + +#import "../../src/parse-formula-intermediate-representation.typ":* +#set page(width: auto, height: auto, margin: 0.5em) + +#[ + $ + #string-to-element("H5+3").at(1)\ + #reaction(string-to-reaction("(H5+3)5+3"))\ + + #reaction(( + molecule((element("C"), + group((element("H", count:2), element("S"), element("O", count:4)), + charge:-2, + // count:5, + ),)), + + arrow(kind:1, top:(molecule((element("H", count:2), element("O")),count:2, ), ), bottom:(element("C", z:6, a:14),)), + + molecule((element("He", count: 2, charge: 1, a: 15, z: 11, oxidation: "+IV"),), + count:2, + phase:"aq", + ),) + ) + + #group(( + group(( + group(( + element("H", count: 2, oxidation: "+I"), + element("O", oxidation: "-II"), + ), + charge: -2, + count: 2, + kind: 1, + ), + element("R", ), + ),count: 2,), + )) + $ +] + diff --git a/tests/get-element/test.typ b/tests/get-element/test.typ index baff382..36e76ee 100644 --- a/tests/get-element/test.typ +++ b/tests/get-element/test.typ @@ -1,4 +1,4 @@ -#import "../../src/lib.typ" : get-element +#import "../../src/lib.typ": get-element #let iron = get-element(symbol: "Fe") #let hydrogen = get-element(common-name: "Hydrogen") diff --git a/tests/intermediate-representation-molecules/ref/1.png b/tests/intermediate-representation-molecules/ref/1.png index 59bf7b8..83cc817 100644 Binary files a/tests/intermediate-representation-molecules/ref/1.png and b/tests/intermediate-representation-molecules/ref/1.png differ diff --git a/tests/intermediate-representation-molecules/test.typ b/tests/intermediate-representation-molecules/test.typ index c8fb9d2..33eafbb 100644 --- a/tests/intermediate-representation-molecules/test.typ +++ b/tests/intermediate-representation-molecules/test.typ @@ -1,90 +1,65 @@ -#import "../../src/display-intermediate-representation.typ" : display-ir +#import "../../src/model/element-element.typ": element +#import "../../src/model/molecule-element.typ": molecule +#import "../../src/model/group-element.typ": group #set page(width: auto, height: auto, margin: 0.5em) -#let co2 = ( - type:"molecule", - count:1, - phase:"g", - charge:0, - align:none, - arrow:none, - children:( - ( - type:"element", - count:1, - symbol:"C", - charge:0, - oxidation-number:none, - isotope:none, - align:none, +#let co2 = molecule( + ( + element( + "C", + count: 1, + charge: 0, + oxidation: none, + a: none, + z: none, ), - ( - type:"element", - count:2, - symbol:"O", - charge:0, - oxidation-number:none, - isotope:none, - align:none, - ) - ) + element( + "O", + count: 2, + charge: 0, + oxidation: none, + a: none, + z: none, + ), + ), + count: 1, + phase: "g", + charge: 0, ) -#let hexacyanidoferrat = ( - type:"molecule", - count:3, - phase:"s", - charge:0, - align:none, - arrow:none, - children:( - ( - type:"group", - count:2, - kind:1, - charge:4, - align:none, - children:( - ( - type:"element", - count:1, - symbol:"Fe", - charge:0, - oxidation-number:none, - isotope:none, - align:none, +#let hexacyanidoferrat = molecule( + ( + group( + ( + element( + "Fe", + count: 1, ), - ( - type:"group", - count:6, - kind:0, - charge:0, - align:none, - children:( - ( - type:"element", - count:1, - symbol:"C", - charge:0, - oxidation-number:none, - isotope:none, - align:none, + group( + ( + element( + "C", + count: 1, ), - ( - type:"element", - count:1, - symbol:"N", - charge:0, - oxidation-number:none, - isotope:none, - align:none, + element( + "N", + count: 1, ), - ) + ), + count: 6, + kind: 0, ), - ) + ), + count: 2, + kind: 1, + charge: 4, ), - ) + ), + count: 3, + phase: "s", + charge: 0, ) -#display-ir(co2)\ -#display-ir(hexacyanidoferrat) \ No newline at end of file +#co2\ +#hexacyanidoferrat +// #display-ir(hexacyanidoferrat) diff --git a/tests/intermediate-representation-reactions/ref/1.png b/tests/intermediate-representation-reactions/ref/1.png deleted file mode 100644 index c3fbed2..0000000 Binary files a/tests/intermediate-representation-reactions/ref/1.png and /dev/null differ diff --git a/tests/intermediate-representation-reactions/test.typ b/tests/intermediate-representation-reactions/test.typ deleted file mode 100644 index c7e294d..0000000 --- a/tests/intermediate-representation-reactions/test.typ +++ /dev/null @@ -1,196 +0,0 @@ -#import "../../src/display-intermediate-representation.typ" : display-ir -#set page(width: auto, height: auto, margin: 0.5em) - -#let reaction1 = ( - ( - type: "molecule", - charge:2, - children:( - ( - type:"group", - kind:1, - children:( - ( - type:"element", - symbol:"Cu", - ), - ( - type:"group", - kind:0, - count:4, - children:( - ( - type:"element", - count:2, - symbol:"H", - ), - ( - type:"element", - symbol:"O", - ), - ) - ), - ) - ), - ) - ), - (type: "align"), - ( - type: "arrow", - kind: 1, - top: none, - bottom: none, - ), - ( - type: "molecule", - charge:2, - children:( - ( - type:"group", - kind:1, - children:( - ( - type:"element", - symbol:"Cu", - ), - ( - type:"group", - kind:0, - count:4, - children:( - ( - type:"element", - symbol:"N", - ), - ( - type:"element", - count:3, - symbol:"H", - ), - ) - ), - ) - ), - ) - ), - (type: "+"), - ( - type: "molecule", - count:4, - children:( - ( - type:"element", - count:2, - symbol:"H", - ), - ( - type:"element", - symbol:"O", - ), - ) - ) -) - -#let reaction2 = ( - ( - type: "molecule", - charge:2, - children:( - ( - type:"group", - kind:1, - children:( - ( - type:"element", - symbol:"Cu", - ), - ( - type:"group", - kind:0, - count:4, - children:( - ( - type:"element", - count:2, - symbol:"H", - ), - ( - type:"element", - symbol:"O", - ), - ) - ), - ) - ), - ) - ), - ( - type:"+" - ), - ( - type: "molecule", - count:4, - children:( - ( - type:"element", - symbol:"N", - ), - ( - type:"element", - count:3, - symbol:"H", - ), - ) - ), - (type: "align"), - ( - type: "arrow", - kind: 1, - top: ( - ( - type:"content", - body:[dissolve in ] - ), - ( - type: "molecule", - children:( - ( - type:"element", - count:2, - symbol:"H", - ), - ( - type:"element", - symbol:"O", - ), - ) - ), - ), - bottom: ( - ( - type:"content", - body:$Delta H^0$ - ), - ), - ), - ( - type: "molecule", - count:4, - children:( - ( - type:"element", - count:2, - symbol:"H", - ), - ( - type:"element", - symbol:"O", - ), - ) - ), -) - -$ - #display-ir(reaction1)\ - #display-ir(reaction2)\ -$ \ No newline at end of file diff --git a/tests/main.typ b/tests/main.typ new file mode 100644 index 0000000..31611d6 --- /dev/null +++ b/tests/main.typ @@ -0,0 +1,161 @@ +// #import "/src/parse-content-intermediate-representation.typ": content-to-ir +// #import "/src/parse-formula-intermediate-representation.typ": string-to-ir +#import "/src/lib.typ": ce +#import "@preview/alchemist:0.1.4": * + +// #let ce(body) = display-ir(string-to-ir(body)) +// #let cem(body) = display-ir(content-to-ir(body)) + +#let alchemist-molecule = skeletize({ + molecule(name: "A", "A") + single() + molecule("B") + branch({ + single(angle: 1) + molecule( + "W", + links: ( + "A": double(stroke: red), + ), + ) + single() + molecule(name: "X", "X") + }) + branch({ + single(angle: -1) + molecule("Y") + single() + molecule( + name: "Z", + "Z", + links: ( + "X": single(stroke: black + 3pt), + ), + ) + }) + single() + molecule( + "C", + links: ( + "X": cram-filled-left(fill: blue), + "Z": single(), + ), + ) +}) +#set page(width: auto, height: auto, margin: 0.5em) + + + +// #cem[H2SO4 + H2O <=[H2O] OH- + #alchemist-molecule + #text("H2O",red)] +// $#cem[#text("H2O",red)]$ +// +// +// #let x = 3 +// #let x = x*5 +// #x + +// #ce[H2O] +// #linebreak() +#let x = ( + ( + type: "molecule", + children: ( + (type: "element", symbol: "H", count: 2, symbol-body:text(red)[H], count-body:math.cancel(angle:50deg)[2]), + (type: "element", symbol: "O"), + ), + ), +) +#let y = ( + ( + type: "molecule", + children: ( + (type: "element", symbol: "Na", symbol-body:strong[N]), + (type: "element", symbol: "H", count: 3, charge:2, count-body:text(green)[3], charge-body:text(red)[2+]), + ), + body:math.cancel(angle:90deg)[] + ), + (type: "+"), + ( + type: "molecule", + children: ( + (type: "element", symbol: "O",symbol-body:text("H",red)), + (type: "element", symbol: "H", charge:-1, symbol-body:text("H",blue)), + ), + body:math.overbrace[#text(red)[OH-]][Hydroxide-ion] + ), +) + +// #text(red)[Hello #text(blue)[World] ] +// #math.cancel(angle: 90deg)[Hello #math.attach("H", br:"ello", tr: "world") world] +// #display-ir(x)\ +// #display-ir(y) +// #let x = math.underbrace[2][Hello World] +// #math.attach("H", br:) +// #linebreak() +$ +#ce("AgCl + 2NH3 &<=> [Ag(NH3)2]+ + Cl-")\ +#ce("Co3^2- + H2O &<=> HCO3- + OH-")\ +#ce("Pb+2 + Co3-2 &-> PbCO3")\ +#ce("Pb+2 + 2OH- &-> Pb(OH)2")\ +#ce("2HClO &->[entwässern] H2O + Cl2O")\ +#ce("3ClO- &->[$Delta$][Disproportionierung] 2Cl- + ClO3- | ")\ +#ce("6KOH + 3Cl2 &->[][Disproportionierung] 5KCl- + KClO3 + H2O")\ +#ce("3HClO3 &->[][Disproportionierung] HClO4 + 2ClO2 + H2O")\ +#ce("6HCl^^+IVO2 + 3H2O &->[][Disproportionierung] 5HCl^^+VO3 + HCl^^-I")\ +#ce("ClO3- + H2SO4&-> HClO3 + HSO4-")\ +#ce("3HClO3 &->[H2SO4] HClO4 + H2O + ClO2 (gelb-grünes gas)")\ +#ce("2ClO2 &->[$Delta$][Explosionsartiger Zerfall] Cl2 + 2O2")\ +#ce("K+ + ClO4- &<=> KClO4")\ +#ce("8Fe(OH)2 + ClO4- + 4H2O &-> Fe(OH)3 + Cl-")\ +#ce("3Ti+3 + ClO4- + 12H2O &-> TiO+2 + Cl- + 8H3O+")\ +#ce("Br2 + 2OH- &-> BrO- + Br- + H2O")\ +#ce("3BrO- &->[$Delta$Raumtemperatur] 2Br- + BrO3-")\ +#ce("3IO- &->[$Delta$Tiefe Temp] 2I- + IO3-")\ +#ce("BrO3- + 5SO2 + 12H2O &-> Br2 + 5SO4-2 + 8H3O+")\ +#ce("Br2 + SO2 + 6H2O &-> 2Br- + 5SO4-3 + 4H3O+")\ +#ce("H2SO4 + 2BrO3- &-> 2HBrO3 + 2HSO4-")\ +#ce("2HBrO3 &->[H2SO4][-H2O] Br2O5")\ +#ce("Br2O5 &->2Br2 + 5O2")\ +#ce("S2- + 2HCl &->H2S + 2Cl")\ +#ce("H2S + Pb(OAc)2 &-> PBS + 2HOAc")\ +#ce("SO3-2 + 2H+ &-> H2O + SO2 (Schwefelpulvergeruch)")\ +#ce("2KHSO4 + SO3-2 &-> K2SO4 + So4-2 + H2O + SO2")\ +#ce("2KHSO4 + SO3-2 &-> K2SO4 + So4-2 + H2O + SO2")\ +#ce("4Zn + NO3- + 7OH- + 6H2O &-> NH3(g) + 4[Zn(OH4)]-2")\ +#ce("NH3 + H2O &<=> NH4+ + OH-")\ +#ce("NH3(g) + konz. HCl(g) &-> NH4Cl")\ +#ce("NO2- &-> HNO3 | 2HNO2 + O2 -> 2HNO3")\ +#ce("2HNO2 + CN2H4O(Harnstoff) &-> CO2 + 2N2")\ +#ce("HNO2 + Sulfonsäure &-> 2N2 + H2SO4")\ +#ce("[Fe(H2O)6]+2 + NO2- + 2H+&-> [Fe((H2O)6)]+3 + NO + H2O")\ +#ce("[Fe(H2O)6]+2 + NO &-> [Fe(H2O)5(NO)]+2 + H2O")\ +#ce("3[Fe(H2O)6]+2 + NO3- + 4H+ &-> [Fe(H2O)6]+3 + NO + 2H2O")\ +#ce("[Fe(H2O)6]+2 + NO &-> [Fe(H2O)5(NO)]+2 + H2O")\ +#ce("Sulfanilsäure + HNO2 + H+ &-> Diazoniumsalze (diazotierung)")\ +#ce("Zn+2 + 2H+ + NO3- &-> Zn+2 + H2O + NO2-")\ +#ce("HPO42- + 23H+ + 3NH4+ + 12MoO4 &-> (NH4)3[P(Mo3O10)4](aq) + 12H2O")\ +#ce("B(OH)3 + 3MeOH &->[konz. H2SO4] B(OMe)3 + 3H2O")\ +//HNO3 + (NH4)6Mo7O24*4H2O +$ + +$ + #ce("H2S &<=> H+ + HS- &&<=> 2H + S2-")\ + #ce("H2O + SO2 &<=> SO2(aq) &&<=> SO2*H2O &&<=> H2SO3")\ + #ce("H2O + SO3 &<=> H2SO4 &&<-> HSO4- &&<-> SO4-2")\ +$ +// #ce[H#text(red)[2]O] +// $#ce[#strike("H2SO4")]$ +// #linebreak() +// #ce[*Fe2* + #[H2O] ] +// #linebreak() +// #linebreak() +// $#ce[12Fe2(SO4)3]$ +// #linebreak() +// #linebreak() +// $#ce[514H2O]$ +// #linebreak() +// #linebreak() +// $#ce[9Fe(OH)3 + ]$ +// #linebreak() +// #linebreak() +// $cem("H2SO4" + "H2O" -/> "[H2O]" "OH-")$ \ No newline at end of file diff --git a/tests/parse-ir-groups/.gitignore b/tests/oxidation-numbers/.gitignore similarity index 88% rename from tests/parse-ir-groups/.gitignore rename to tests/oxidation-numbers/.gitignore index 03681f9..40223be 100644 --- a/tests/parse-ir-groups/.gitignore +++ b/tests/oxidation-numbers/.gitignore @@ -2,4 +2,3 @@ diff/** out/** -ref/** diff --git a/tests/oxidation-numbers/ref/1.png b/tests/oxidation-numbers/ref/1.png new file mode 100644 index 0000000..b5ce16b Binary files /dev/null and b/tests/oxidation-numbers/ref/1.png differ diff --git a/tests/oxidation-numbers/test.typ b/tests/oxidation-numbers/test.typ new file mode 100644 index 0000000..3943859 --- /dev/null +++ b/tests/oxidation-numbers/test.typ @@ -0,0 +1,24 @@ +#import "../../src/lib.typ": * +// #import "../../src/libs/elembic/lib.typ" as e +#import "../../src/model/element-element.typ": * + +#set page(width: auto, height: auto, margin: 0.5em) + +// #ce("O^^-ii") +// #ce("S^^+VI") +// #ce("C^^+4") +// #ce("H^^+1") + +// #show: set-element(roman-oxidation: false) +// #ce("O^^-2") +// #ce("S^^+6") +// #ce("C^^+IV") +// #ce("H^^+I") + +// #show: set-element(roman-oxidation: true) +// #ce("O^1^^-2") +// #ce("S^2^^+6") +#ce("2H^.2-^^+1") + +// #ce("C^-2^^+4") +// #ce("H_3^^+IO+^^-2") diff --git a/tests/parse-ir-elements/test.typ b/tests/parse-ir-elements/test.typ deleted file mode 100644 index 0b48ce8..0000000 --- a/tests/parse-ir-elements/test.typ +++ /dev/null @@ -1,79 +0,0 @@ -#import "../../src/parse-formula-intermediate-representation.typ" : molecule-string-to-ir -#import "../../src/lib.typ" : display-ir -#set page(width: auto, height: auto, margin: 0.5em) - -#let co2 = ( - type: "molecule", - children: ( - (type: "element", symbol: "C"), - (type: "element", symbol: "O", count: 2), - ), - ) -#let ir-co2 = molecule-string-to-ir("CO2") - -#let no = ( - type: "molecule", - children: ( - (type: "element", symbol: "N"), - ( - type: "element", - symbol: "O", - charge: -2, - radical: true, - ), - ), - ) -#let ir-no = molecule-string-to-ir("NO^2.-") - -#let na = ( - type: "molecule", - children: ( - (type: "element", symbol: "Na", count: 3, charge: 1), - ), - ) -#let ir-na1 = molecule-string-to-ir("Na_3^+") -#let ir-na2 = molecule-string-to-ir("Na_3^+") - -#let cl = ( - type: "molecule", - children: ( - ( - type: "element", - symbol: "Cl", - count: 2, - charge: -1, - ), - ), - ) -#let ir-cl = molecule-string-to-ir("Cl2-1") - -#let fe = ( - type: "molecule", - children: ( - ( - type: "element", - symbol: "Fe", - count: 2, - charge: "III", - ), - ), - ) -#let ir-fe = molecule-string-to-ir("Fe2^III") - -#display-ir(ir-co2) -#display-ir(ir-no) -#display-ir(ir-cl) -#display-ir(ir-fe) -#display-ir(ir-na1) -#display-ir(ir-na2)\ -#display-ir(co2) -#display-ir(no) -#display-ir(cl) -#display-ir(fe) -#display-ir(na) -#assert(co2 == ir-co2) -#assert(no == ir-no) -#assert(na == ir-na1) -#assert(na == ir-na2) -#assert(cl == ir-cl) -#assert(fe == ir-fe) \ No newline at end of file diff --git a/tests/parse-ir-groups/test.typ b/tests/parse-ir-groups/test.typ deleted file mode 100644 index b171a3d..0000000 --- a/tests/parse-ir-groups/test.typ +++ /dev/null @@ -1,43 +0,0 @@ -#import "../../src/parse-formula-intermediate-representation.typ" : molecule-string-to-ir - -#let trisethylendiamin = ( - type: "molecule", - children: ( - ( - type: "group", - kind: 1, - children: ( - (type: "element", symbol: "Co"), - ( - type: "group", - kind: 0, - children: ((type: "content", body: [en]),), - count: 3, - ), - ), - ), - (type: "element", symbol: "Cl", count: 3), - ), - ) -#let ir-trisethylendiamin = molecule-string-to-ir("[Co(en)3]Cl3") - -#let fenh3 = ( - type: "molecule", - children: ( - (type: "element", symbol: "Fe"), - ( - type: "group", - kind: 1, - children: ( - (type: "element", symbol: "N"), - (type: "element", symbol: "H", count: 3), - ), - count: 2, - charge: 1, - ), - ), - ) -#let ir-fenh3 = molecule-string-to-ir("Fe[NH3]2+") - -#assert(trisethylendiamin == ir-trisethylendiamin) -#assert(fenh3 == ir-fenh3) \ No newline at end of file diff --git a/tests/reactions/.gitignore b/tests/reactions/.gitignore new file mode 100644 index 0000000..40223be --- /dev/null +++ b/tests/reactions/.gitignore @@ -0,0 +1,4 @@ +# generated by tytanic, do not edit + +diff/** +out/** diff --git a/tests/reactions/ref/1.png b/tests/reactions/ref/1.png new file mode 100644 index 0000000..973af50 Binary files /dev/null and b/tests/reactions/ref/1.png differ diff --git a/tests/reactions/test.typ b/tests/reactions/test.typ new file mode 100644 index 0000000..42d6518 --- /dev/null +++ b/tests/reactions/test.typ @@ -0,0 +1,9 @@ +#import "../../src/lib.typ" : ce +// #import "../../src/libs/elembic/lib.typ" as e +// #import "../../src/model/group.typ":* +// #show: e.set_(group, grow-brackets:false, affect-layout:false) + +#set page(width: auto, height: auto, margin: 0.5em) + +// #ce("A + B =>[H2SO4][Hello World] C + D")\ +#ce("[Cu(H2O)4]^2 + 4NH3 ->[dissolve in H2O][$Delta H^0$] [Cu(NH3)4]^+2 4H2O")\ \ No newline at end of file diff --git a/tests/shell-configuration/test.typ b/tests/shell-configuration/test.typ index f0d7e80..7ccdbb0 100644 --- a/tests/shell-configuration/test.typ +++ b/tests/shell-configuration/test.typ @@ -1,8 +1,12 @@ -#import "../../src/display-shell-configuration.typ" : get-shell-configuration, display-electron-configuration, get-electron-configuration -#import "../../src/lib.typ" : get-element +#import "../../src/display-shell-configuration.typ": ( + get-shell-configuration, + display-electron-configuration, + get-electron-configuration, +) +#import "../../src/lib.typ": get-element #set page(width: auto, height: auto, margin: 0.5em) -#let carbon = get-element(symbol:"Y") +#let carbon = get-element(symbol: "Y") #let shells = get-shell-configuration(carbon) #let orbitals = get-electron-configuration(carbon) #display-electron-configuration(carbon) diff --git a/tests/show-rule/.gitignore b/tests/show-rule/.gitignore new file mode 100644 index 0000000..40223be --- /dev/null +++ b/tests/show-rule/.gitignore @@ -0,0 +1,4 @@ +# generated by tytanic, do not edit + +diff/** +out/** diff --git a/tests/show-rule/ref/1.png b/tests/show-rule/ref/1.png new file mode 100644 index 0000000..3d033ac Binary files /dev/null and b/tests/show-rule/ref/1.png differ diff --git a/tests/show-rule/test.typ b/tests/show-rule/test.typ new file mode 100644 index 0000000..899ffd3 --- /dev/null +++ b/tests/show-rule/test.typ @@ -0,0 +1,9 @@ +#import "../../src/lib.typ" : * + +#set page(width: auto, height: auto, margin: 0.5em) + +// #show: set-group(grow-brackets:false, affect-layout:false) +// #show: set-element(roman-charge:true) +#ce("Cu-2^^2O^^-2 + H2^^0 &-> Cu^^0 + H2^^1O^^-2")\ +#show: set-element(roman-oxidation:true,)//affect-layout:false) +#ce("Cu-2^^2O^^-2 + H2^^0 &-> Cu^^0 + H2^^1O^^-2") \ No newline at end of file diff --git a/tests/simple-formulas/test.typ b/tests/simple-formulas/test.typ index 10ef719..bd6fc65 100644 --- a/tests/simple-formulas/test.typ +++ b/tests/simple-formulas/test.typ @@ -1,5 +1,4 @@ -#import "../../src/lib.typ" : ce -//#import "@preview/whalogen:0.3.0": ce +#import "../../src/lib.typ": ce #set page(width: auto, height: auto, margin: 0.5em) diff --git a/typst.toml b/typst.toml index 070b84c..f57525f 100644 --- a/typst.toml +++ b/typst.toml @@ -1,6 +1,6 @@ [package] name = "typsium" -version = "0.2.0" +version = "0.3.0" repository = "https://github.com/Typsium/typsium" license = "MIT" entrypoint = "src/lib.typ" @@ -9,7 +9,8 @@ authors = [ "β-吲哚基丙氨酸 ", "Ants Aare Alamaa <@Ants-Aare>" ] +compiler = "0.13.1" description = "Typeset chemical formulas and reactions." categories = [ "text", "paper" ] disciplines = [ "education", "chemistry" ] -keywords = ["chemistry", "chemical", "reaction", "formula", "stochiometry", "oxidation", "equation", "electron", "isotope", "molecule","atom", "hazard", "precaution", "h and p", "mhchem"] \ No newline at end of file +keywords = ["chemistry", "chemical", "biotech","organic", "reaction", "formula", "stochiometry", "oxidation", "equation", "isotope", "molecule","atom", "mhchem"] \ No newline at end of file