Compare commits
62 Commits
@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Write...
|
||||
2. Render...
|
||||
3. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Arch Linux]
|
||||
- Architecture: [e.g. x86_64, ARM]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
@ -0,0 +1,42 @@
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache build data
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Build
|
||||
run: cargo build --verbose --all-features
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --verbose --all-features
|
||||
|
||||
- name: Test init
|
||||
run: cargo run -- init
|
||||
|
||||
- name: Test HTML
|
||||
run: cargo run -- render README.md README.html --format html
|
||||
|
||||
- name: Test PDF
|
||||
run: cargo run --all-features -- render README.md README.pdf --format pdf
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,167 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: $body-background;
|
||||
overflow-x: hidden;
|
||||
color: $primary-color;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif;
|
||||
width: 100vh;
|
||||
max-width: calc(100% - 4rem);
|
||||
padding: 2rem;
|
||||
margin: auto;
|
||||
background-color: $background-color;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.4rem;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
color: $primary-color;
|
||||
|
||||
pre {
|
||||
font-family: "Fira Code", "Mono", monospace;
|
||||
padding: 0.8em 0.2em;
|
||||
background-color: $code-background !important;
|
||||
border-radius: 0.25em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.inlineCode {
|
||||
font-family: "Fira Code", monospace;
|
||||
border-radius: 0.1em;
|
||||
background-color: $code-background;
|
||||
padding: 0 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
|
||||
& > table {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
|
||||
tr {
|
||||
&:nth-child(odd) {
|
||||
background-color: $table-background-alt;
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
background-color: $table-background-alt;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid invert($background-color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
border-left: 1px solid invert($background-color);
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
|
||||
table tr td:first-child, table tr th:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 0;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
a {
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.quote {
|
||||
border-left: 0.3em solid $quote-background-alt;
|
||||
border-radius: 0.2em;
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
background-color: $quote-background;
|
||||
|
||||
.metadata {
|
||||
font-style: italic;
|
||||
padding-left: 0.5em;
|
||||
color: $primary-variant-1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.figure {
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
.imageDescription {
|
||||
display: block;
|
||||
color: $primary-variant-1;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.glossaryReference {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
border-bottom: 1px dotted $primary-color;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-family: "Fira Code", "Mono", monospace;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
.content > section > section, .content > section > section {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: $background-color !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
$background-color: #1e1d2c;
|
||||
$background-color-variant-1: lighten($background-color, 7%);
|
||||
$background-color-variant-2: lighten($background-color, 14%);
|
||||
$background-color-variant-3: lighten($background-color, 21%);
|
||||
|
||||
$primary-color: #EEE;
|
||||
$primary-variant-1: darken($primary-color, 14%);
|
||||
$secondary-color: #3aa7df;
|
||||
|
||||
$body-background: darken($background-color, 5%);
|
||||
$code-background: lighten($background-color, 5%);
|
||||
$table-background-alt: $background-color-variant-2;
|
||||
$quote-background: lighten($background-color-variant-1, 3%);
|
||||
$quote-background-alt: $background-color-variant-3;
|
@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
$background-color: darken(#2b303b, 8%);
|
||||
$background-color-variant-1: lighten($background-color, 7%);
|
||||
$background-color-variant-2: lighten($background-color, 14%);
|
||||
$background-color-variant-3: lighten($background-color, 21%);
|
||||
|
||||
$primary-color: #EEE;
|
||||
$primary-variant-1: darken($primary-color, 14%);
|
||||
$secondary-color: #3aa7df;
|
||||
|
||||
$body-background: darken($background-color, 5%);
|
||||
$code-background: $background-color-variant-1;
|
||||
$table-background-alt: $background-color-variant-2;
|
||||
$quote-background: lighten($background-color-variant-1, 3%);
|
||||
$quote-background-alt: $background-color-variant-3;
|
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
$background-color: darken(#002b36, 5%);
|
||||
$background-color-variant-1: lighten($background-color, 7%);
|
||||
$background-color-variant-2: lighten($background-color, 14%);
|
||||
$background-color-variant-3: lighten($background-color, 21%);
|
||||
$primary-color: #EEE;
|
||||
$primary-variant-1: darken($primary-color, 14%);
|
||||
$secondary-color: #0096c9;
|
||||
|
||||
$body-background: darken($background-color, 3%);
|
||||
$code-background: $background-color-variant-1;
|
||||
$table-background-alt: lighten($background-color, 10%);
|
||||
$quote-background: lighten($background-color-variant-1, 3%);
|
||||
$quote-background-alt: $background-color-variant-3;
|
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
$background-color: #FFF;
|
||||
$background-color-variant-1: darken($background-color, 7%);
|
||||
$background-color-variant-2: darken($background-color, 14%);
|
||||
$background-color-variant-3: darken($background-color, 21%);
|
||||
$primary-color: #000;
|
||||
$primary-variant-1: lighten($primary-color, 14%);
|
||||
$secondary-color: #00286a;
|
||||
|
||||
$body-background: $background-color-variant-1;
|
||||
$code-background: $background-color-variant-1;
|
||||
$table-background-alt: $background-color-variant-2;
|
||||
$quote-background: $background-color-variant-2;
|
||||
$quote-background-alt: $background-color-variant-3;
|
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
$background-color: #FFF;
|
||||
$background-color-variant-1: darken($background-color, 7%);
|
||||
$background-color-variant-2: darken($background-color, 14%);
|
||||
$background-color-variant-3: darken($background-color, 21%);
|
||||
$primary-color: #112;
|
||||
$primary-variant-1: lighten($primary-color, 14%);
|
||||
$secondary-color: #00348e;
|
||||
|
||||
$body-background: $background-color-variant-1;
|
||||
$code-background: $background-color-variant-1;
|
||||
$table-background-alt: $background-color-variant-2;
|
||||
$quote-background: $background-color-variant-2;
|
||||
$quote-background-alt: $background-color-variant-3;
|
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
$background-color: #fff8f0;
|
||||
$background-color-variant-1: darken($background-color, 4%);
|
||||
$background-color-variant-2: darken($background-color, 8%);
|
||||
$background-color-variant-3: darken($background-color, 12%);
|
||||
$primary-color: #112;
|
||||
$primary-variant-1: lighten($primary-color, 14%);
|
||||
$secondary-color: #2b61be;
|
||||
|
||||
$body-background: $background-color-variant-1;
|
||||
$code-background: $background-color-variant-1;
|
||||
$table-background-alt: $background-color-variant-2;
|
||||
$quote-background: $background-color-variant-2;
|
||||
$quote-background-alt: $background-color-variant-3;
|
@ -1,3 +1,9 @@
|
||||
<!--
|
||||
~ Snekdown - Custom Markdown flavour and parser
|
||||
~ Copyright (C) 2021 Trivernis
|
||||
~ See LICENSE for more information.
|
||||
-->
|
||||
|
||||
<div style="font-size: 10px; text-align: center; width: 100%;">
|
||||
<span class="pageNumber"></span>/<span class="totalPages"></span>
|
||||
</div>
|
@ -1,149 +0,0 @@
|
||||
body {
|
||||
background-color: #DDD;
|
||||
overflow-x: hidden;
|
||||
color: #000;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif;
|
||||
width: 100vh;
|
||||
max-width: calc(100% - 4rem);
|
||||
padding: 2rem;
|
||||
margin: auto;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.4rem;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
code pre {
|
||||
font-family: "Fira Code", "Mono", monospace;
|
||||
padding: 0.8em 0.2em;
|
||||
background-color: #EEE !important;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
|
||||
code.inlineCode {
|
||||
font-family: "Fira Code", monospace;
|
||||
border-radius: 0.1em;
|
||||
background-color: #EEE;
|
||||
padding: 0 0.1em
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tableWrapper > table {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table tr:nth-child(odd) {
|
||||
background-color: #DDD;
|
||||
}
|
||||
|
||||
table tr:nth-child(1) {
|
||||
background-color: white;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
border-left: 1px solid black;
|
||||
padding: 0.2em 0.5em
|
||||
}
|
||||
|
||||
table tr td:first-child, table tr th:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.quote {
|
||||
border-left: 0.3em solid gray;
|
||||
border-radius: 0.2em;
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.quote .metadata {
|
||||
font-style: italic;
|
||||
padding-left: 0.5em;
|
||||
color: #444
|
||||
}
|
||||
|
||||
.figure {
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.figure .imageDescription {
|
||||
display: block;
|
||||
color: #444;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.glossaryReference {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
border-bottom: 1px dotted #000;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-family: "Fira Code", "Mono", monospace;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.content > section > section, .content > section > section {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
body {
|
||||
background-color: white !important;
|
||||
}
|
||||
}
|
@ -1,2 +1,8 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
pub mod html_writer;
|
||||
pub mod to_html;
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use crate::settings::style_settings::Theme;
|
||||
use std::time::Instant;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
/// Returns the css of a theme compiled from sass
|
||||
pub fn get_css_for_theme(theme: Theme) -> String {
|
||||
let start = Instant::now();
|
||||
let vars = match theme {
|
||||
Theme::GitHub => include_str!("assets/light-github.scss"),
|
||||
Theme::SolarizedDark => include_str!("assets/dark-solarized.scss"),
|
||||
Theme::SolarizedLight => include_str!("assets/light-solarized.scss"),
|
||||
Theme::OceanDark => include_str!("assets/dark-ocean.scss"),
|
||||
Theme::OceanLight => include_str!("assets/light-ocean.scss"),
|
||||
Theme::MagicDark => include_str!("assets/dark-magic.scss"),
|
||||
};
|
||||
let style = format!("{}\n{}", vars, include_str!("assets/base.scss"));
|
||||
|
||||
let css = compile_sass(&*style);
|
||||
|
||||
log::debug!("Compiled style in {} ms", start.elapsed().as_millis());
|
||||
|
||||
css
|
||||
}
|
||||
|
||||
/// Returns the syntax theme for a given theme
|
||||
pub fn get_code_theme_for_theme(theme: Theme) -> (syntect::highlighting::Theme, SyntaxSet) {
|
||||
lazy_static::lazy_static! { static ref PS: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); }
|
||||
lazy_static::lazy_static! { static ref TS: ThemeSet = ThemeSet::load_defaults(); }
|
||||
|
||||
let theme = match theme {
|
||||
Theme::GitHub => "InspiredGitHub",
|
||||
Theme::SolarizedDark => "Solarized (dark)",
|
||||
Theme::SolarizedLight => "Solarized (light)",
|
||||
Theme::OceanDark => "base16-ocean.dark",
|
||||
Theme::OceanLight => "base16-ocean.light",
|
||||
Theme::MagicDark => "base16-ocean.dark",
|
||||
};
|
||||
|
||||
return (TS.themes[theme].clone(), PS.clone());
|
||||
}
|
||||
|
||||
fn compile_sass(sass: &str) -> String {
|
||||
String::from_utf8(
|
||||
rsass::compile_scss(
|
||||
sass.as_bytes(),
|
||||
rsass::output::Format {
|
||||
style: rsass::output::Style::Compressed,
|
||||
precision: 5,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#![allow(unused)]
|
||||
|
||||
pub const BIB_REF_DISPLAY: &str = "bib-ref-display";
|
||||
pub const META_LANG: &str = "language";
|
||||
|
||||
// import and include options
|
||||
pub const IMP_IGNORE: &str = "ignored-imports";
|
||||
pub const IMP_STYLESHEETS: &str = "included-stylesheets";
|
||||
pub const IMP_CONFIGS: &str = "included-configs";
|
||||
pub const IMP_BIBLIOGRAPHY: &str = "included-bibliography";
|
||||
pub const IMP_GLOSSARY: &str = "included-glossary";
|
||||
pub const EMBED_EXTERNAL: &str = "embed-external";
|
||||
pub const SMART_ARROWS: &str = "smart-arrows";
|
||||
pub const INCLUDE_MATHJAX: &str = "include-math-jax";
|
||||
|
||||
// PDF options
|
||||
pub const PDF_DISPLAY_HEADER_FOOTER: &str = "pfd-display-header-footer";
|
||||
pub const PDF_HEADER_TEMPLATE: &str = "pdf-header-template";
|
||||
pub const PDF_FOOTER_TEMPLATE: &str = "pdf-footer-template";
|
||||
pub const PDF_MARGIN_TOP: &str = "pdf-margin-top";
|
||||
pub const PDF_MARGIN_BOTTOM: &str = "pdf-margin-bottom";
|
||||
pub const PDF_MARGIN_LEFT: &str = "pdf-margin-left";
|
||||
pub const PDF_MARGIN_RIGHT: &str = "pdf-margin-right";
|
||||
pub const PDF_PAGE_HEIGHT: &str = "pdf-page-height";
|
||||
pub const PDF_PAGE_WIDTH: &str = "pdf-page-width";
|
||||
pub const PDF_PAGE_SCALE: &str = "pdf-page-scale";
|
@ -1,188 +0,0 @@
|
||||
use crate::elements::MetadataValue;
|
||||
use crate::references::configuration::keys::{
|
||||
BIB_REF_DISPLAY, META_LANG, PDF_DISPLAY_HEADER_FOOTER, PDF_FOOTER_TEMPLATE,
|
||||
PDF_HEADER_TEMPLATE, PDF_MARGIN_BOTTOM, PDF_MARGIN_TOP,
|
||||
};
|
||||
use crate::references::templates::Template;
|
||||
use serde::export::TryFrom;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub(crate) mod keys;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Bool(bool),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
Template(Template),
|
||||
Array(Vec<Value>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConfigEntry {
|
||||
inner: Value,
|
||||
}
|
||||
|
||||
pub type ConfigRefEntry = Arc<RwLock<ConfigEntry>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Configuration {
|
||||
config: Arc<RwLock<HashMap<String, ConfigRefEntry>>>,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn as_string(&self) -> String {
|
||||
match self {
|
||||
Value::String(string) => string.clone(),
|
||||
Value::Integer(int) => format!("{}", int),
|
||||
Value::Float(f) => format!("{:02}", f),
|
||||
Value::Bool(b) => format!("{}", b),
|
||||
Value::Array(a) => a.iter().fold("".to_string(), |a, b| {
|
||||
format!("{} \"{}\"", a, b.as_string())
|
||||
}),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bool value if the value is a boolean
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
Value::Bool(b) => Some(*b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match self {
|
||||
Value::Float(v) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigEntry {
|
||||
pub fn new(value: Value) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: Value) {
|
||||
self.inner = value;
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &Value {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Configuration {
|
||||
fn default() -> Self {
|
||||
let mut self_config = Self::new();
|
||||
self_config.set(BIB_REF_DISPLAY, Value::String("{{number}}".to_string()));
|
||||
self_config.set(META_LANG, Value::String("en".to_string()));
|
||||
self_config.set(PDF_MARGIN_BOTTOM, Value::Float(0.5));
|
||||
self_config.set(PDF_MARGIN_TOP, Value::Float(0.5));
|
||||
self_config.set(PDF_DISPLAY_HEADER_FOOTER, Value::Bool(true));
|
||||
self_config.set(
|
||||
PDF_HEADER_TEMPLATE,
|
||||
Value::String("<div></div>".to_string()),
|
||||
);
|
||||
self_config.set(
|
||||
PDF_FOOTER_TEMPLATE,
|
||||
Value::String(
|
||||
include_str!("../../format/chromium_pdf/assets/default-footer-template.html")
|
||||
.to_string(),
|
||||
),
|
||||
);
|
||||
|
||||
self_config
|
||||
}
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the value of a config entry
|
||||
pub fn get_entry(&self, key: &str) -> Option<ConfigEntry> {
|
||||
let config = self.config.read().unwrap();
|
||||
if let Some(entry) = config.get(key) {
|
||||
let value = entry.read().unwrap();
|
||||
Some(value.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// returns a config entry that is a reference to a value
|
||||
pub fn get_ref_entry(&self, key: &str) -> Option<ConfigRefEntry> {
|
||||
let config = self.config.read().unwrap();
|
||||
if let Some(entry) = config.get(&key.to_string()) {
|
||||
Some(Arc::clone(entry))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a config parameter
|
||||
pub fn set(&mut self, key: &str, value: Value) {
|
||||
let mut config = self.config.write().unwrap();
|
||||
if let Some(entry) = config.get(&key.to_string()) {
|
||||
entry.write().unwrap().set(value)
|
||||
} else {
|
||||
config.insert(
|
||||
key.to_string(),
|
||||
Arc::new(RwLock::new(ConfigEntry::new(value))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a config value based on a metadata value
|
||||
pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) {
|
||||
match value {
|
||||
MetadataValue::String(string) => self.set(key, Value::String(string)),
|
||||
MetadataValue::Bool(bool) => self.set(key, Value::Bool(bool)),
|
||||
MetadataValue::Float(f) => self.set(key, Value::Float(f)),
|
||||
MetadataValue::Integer(i) => self.set(key, Value::Integer(i)),
|
||||
MetadataValue::Template(t) => self.set(key, Value::Template(t)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_from_toml(&mut self, value: &toml::Value) -> Option<()> {
|
||||
let table = value.as_table().cloned()?;
|
||||
table.iter().for_each(|(k, v)| {
|
||||
match v {
|
||||
toml::Value::Table(_) => self.set_from_toml(v).unwrap_or(()),
|
||||
_ => self.set(k, Value::try_from(v.clone()).unwrap()),
|
||||
};
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<toml::Value> for Value {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: toml::Value) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
toml::Value::Table(_) => Err(()),
|
||||
toml::Value::Float(f) => Ok(Value::Float(f)),
|
||||
toml::Value::Integer(i) => Ok(Value::Integer(i)),
|
||||
toml::Value::String(s) => Ok(Value::String(s)),
|
||||
toml::Value::Boolean(b) => Ok(Value::Bool(b)),
|
||||
toml::Value::Datetime(dt) => Ok(Value::String(dt.to_string())),
|
||||
toml::Value::Array(a) => Ok(Value::Array(
|
||||
a.iter()
|
||||
.cloned()
|
||||
.filter_map(|e| Value::try_from(e).ok())
|
||||
.collect::<Vec<Value>>(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
pub mod bibliography;
|
||||
pub mod configuration;
|
||||
pub mod glossary;
|
||||
pub mod placeholders;
|
||||
pub mod templates;
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct FeatureSettings {
|
||||
pub embed_external: bool,
|
||||
pub smart_arrows: bool,
|
||||
pub include_mathjax: bool,
|
||||
}
|
||||
|
||||
impl Default for FeatureSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
embed_external: true,
|
||||
smart_arrows: true,
|
||||
include_mathjax: true,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ImageSettings {
|
||||
pub format: Option<String>,
|
||||
pub max_width: Option<u32>,
|
||||
pub max_height: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for ImageSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
format: None,
|
||||
max_height: None,
|
||||
max_width: None,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ImportSettings {
|
||||
pub ignored_imports: Vec<String>,
|
||||
pub included_stylesheets: Vec<String>,
|
||||
pub included_bibliography: Vec<String>,
|
||||
pub included_glossaries: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for ImportSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ignored_imports: Vec::with_capacity(0),
|
||||
included_stylesheets: vec!["style.css".to_string()],
|
||||
included_bibliography: vec!["Bibliography.toml".to_string()],
|
||||
included_glossaries: vec!["Glossary.toml".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct MetadataSettings {
|
||||
pub title: Option<String>,
|
||||
pub author: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub keywords: Vec<String>,
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
impl Default for MetadataSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
author: None,
|
||||
description: None,
|
||||
keywords: Vec::new(),
|
||||
language: "en".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use crate::elements::{Metadata, MetadataValue};
|
||||
use crate::settings::feature_settings::FeatureSettings;
|
||||
use crate::settings::image_settings::ImageSettings;
|
||||
use crate::settings::import_settings::ImportSettings;
|
||||
use crate::settings::metadata_settings::MetadataSettings;
|
||||
use crate::settings::pdf_settings::PDFSettings;
|
||||
use crate::settings::style_settings::StyleSettings;
|
||||
use config::{ConfigError, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod feature_settings;
|
||||
pub mod image_settings;
|
||||
pub mod import_settings;
|
||||
pub mod metadata_settings;
|
||||
pub mod pdf_settings;
|
||||
pub mod style_settings;
|
||||
|
||||
pub type SettingsResult<T> = Result<T, SettingsError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SettingsError {
|
||||
IoError(io::Error),
|
||||
ConfigError(ConfigError),
|
||||
TomlError(toml::ser::Error),
|
||||
}
|
||||
|
||||
impl Display for SettingsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::IoError(e) => write!(f, "IO Error: {}", e),
|
||||
Self::ConfigError(e) => write!(f, "Config Error: {}", e),
|
||||
Self::TomlError(e) => write!(f, "Toml Error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SettingsError {}
|
||||
|
||||
impl From<io::Error> for SettingsError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigError> for SettingsError {
|
||||
fn from(e: ConfigError) -> Self {
|
||||
Self::ConfigError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::ser::Error> for SettingsError {
|
||||
fn from(e: toml::ser::Error) -> Self {
|
||||
Self::TomlError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct Settings {
|
||||
pub metadata: MetadataSettings,
|
||||
pub features: FeatureSettings,
|
||||
pub imports: ImportSettings,
|
||||
pub pdf: PDFSettings,
|
||||
pub images: ImageSettings,
|
||||
pub style: StyleSettings,
|
||||
pub custom_attributes: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Source for Settings {
|
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn collect(&self) -> Result<HashMap<String, config::Value>, config::ConfigError> {
|
||||
let source_str =
|
||||
toml::to_string(&self).map_err(|e| config::ConfigError::Foreign(Box::new(e)))?;
|
||||
let result = toml::de::from_str(&source_str)
|
||||
.map_err(|e| config::ConfigError::Foreign(Box::new(e)))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Loads the settings from the specified path
|
||||
pub fn load(path: PathBuf) -> SettingsResult<Self> {
|
||||
let mut settings = config::Config::default();
|
||||
settings
|
||||
.merge(Self::default())?
|
||||
.merge(config::File::from(path))?;
|
||||
let settings: Self = settings.try_into()?;
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
/// Merges the current settings with the settings from the given path
|
||||
/// returning updated settings
|
||||
pub fn merge(&mut self, path: PathBuf) -> SettingsResult<()> {
|
||||
let mut settings = config::Config::default();
|
||||
settings
|
||||
.merge(self.clone())?
|
||||
.merge(config::File::from(path))?;
|
||||
let mut settings: Self = settings.try_into()?;
|
||||
mem::swap(self, &mut settings); // replace the old settings with the new ones
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn append_metadata<M: Metadata>(&mut self, metadata: M) {
|
||||
let entries = metadata.get_string_map();
|
||||
for (key, value) in entries {
|
||||
self.custom_attributes.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) {
|
||||
self.custom_attributes
|
||||
.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct PDFSettings {
|
||||
pub display_header_footer: bool,
|
||||
pub header_template: Option<String>,
|
||||
pub footer_template: Option<String>,
|
||||
pub page_height: Option<f32>,
|
||||
pub page_width: Option<f32>,
|
||||
pub page_scale: f32,
|
||||
pub margin: PDFMarginSettings,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct PDFMarginSettings {
|
||||
pub top: Option<f32>,
|
||||
pub bottom: Option<f32>,
|
||||
pub left: Option<f32>,
|
||||
pub right: Option<f32>,
|
||||
}
|
||||
|
||||
impl Default for PDFMarginSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
top: Some(0.5),
|
||||
bottom: Some(0.5),
|
||||
left: None,
|
||||
right: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PDFSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
display_header_footer: true,
|
||||
header_template: Some("<div></div>".to_string()),
|
||||
footer_template: Some(
|
||||
include_str!("../format/chromium_pdf/assets/default-footer-template.html")
|
||||
.to_string(),
|
||||
),
|
||||
page_height: None,
|
||||
page_width: None,
|
||||
page_scale: 1.0,
|
||||
margin: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct StyleSettings {
|
||||
pub bib_ref_display: String,
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
impl Default for StyleSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bib_ref_display: "{{number}}".to_string(),
|
||||
theme: Theme::GitHub,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum Theme {
|
||||
GitHub,
|
||||
SolarizedDark,
|
||||
SolarizedLight,
|
||||
OceanDark,
|
||||
OceanLight,
|
||||
MagicDark,
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use platform_dirs::{AppDirs, AppUI};
|
||||
use sha2::Digest;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CacheStorage {
|
||||
location: PathBuf,
|
||||
}
|
||||
|
||||
impl CacheStorage {
|
||||
pub fn new() -> Self {
|
||||
lazy_static::lazy_static! {
|
||||
static ref APP_DIRS: AppDirs = AppDirs::new(Some("snekdown"), AppUI::CommandLine).unwrap();
|
||||
}
|
||||
|
||||
Self {
|
||||
location: APP_DIRS.cache_dir.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cache path for a given file
|
||||
pub fn get_file_path(&self, path: &PathBuf) -> PathBuf {
|
||||
let mut hasher = sha2::Sha256::default();
|
||||
hasher.update(path.to_string_lossy().as_bytes());
|
||||
let mut file_name = PathBuf::from(format!("{:x}", hasher.finalize()));
|
||||
|
||||
if let Some(extension) = path.extension() {
|
||||
file_name.set_extension(extension);
|
||||
}
|
||||
log::trace!("Cache path is {:?}", path);
|
||||
|
||||
return self.location.join(PathBuf::from(file_name));
|
||||
}
|
||||
|
||||
/// Returns if the given file exists in the cache
|
||||
pub fn has_file(&self, path: &PathBuf) -> bool {
|
||||
let cache_path = self.get_file_path(path);
|
||||
|
||||
cache_path.exists()
|
||||
}
|
||||
|
||||
/// Writes into the corresponding cache file
|
||||
pub fn read(&self, path: &PathBuf) -> io::Result<Vec<u8>> {
|
||||
let cache_path = self.get_file_path(path);
|
||||
|
||||
fs::read(cache_path)
|
||||
}
|
||||
|
||||
/// Reads the corresponding cache file
|
||||
pub fn write<R: AsRef<[u8]>>(&self, path: &PathBuf, contents: R) -> io::Result<()> {
|
||||
let cache_path = self.get_file_path(path);
|
||||
|
||||
fs::write(cache_path, contents)
|
||||
}
|
||||
|
||||
/// Clears the cache directory by deleting and recreating it
|
||||
pub fn clear(&self) -> io::Result<()> {
|
||||
fs::remove_dir_all(&self.location)?;
|
||||
fs::create_dir(&self.location)
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use crate::elements::Metadata;
|
||||
use crate::utils::caching::CacheStorage;
|
||||
use crate::utils::downloads::download_path;
|
||||
use image::imageops::FilterType;
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{GenericImageView, ImageFormat, ImageResult};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use mime::Mime;
|
||||
use parking_lot::Mutex;
|
||||
use rayon::prelude::*;
|
||||
use std::io;
|
||||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageConverter {
|
||||
images: Vec<Arc<Mutex<PendingImage>>>,
|
||||
target_format: Option<ImageFormat>,
|
||||
target_size: Option<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl ImageConverter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
images: Vec::new(),
|
||||
target_format: None,
|
||||
target_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_target_size(&mut self, target_size: (u32, u32)) {
|
||||
self.target_size = Some(target_size)
|
||||
}
|
||||
|
||||
pub fn set_target_format(&mut self, target_format: ImageFormat) {
|
||||
self.target_format = Some(target_format);
|
||||
}
|
||||
|
||||
/// Adds an image to convert
|
||||
pub fn add_image(&mut self, path: PathBuf) -> Arc<Mutex<PendingImage>> {
|
||||
let image = Arc::new(Mutex::new(PendingImage::new(path)));
|
||||
self.images.push(image.clone());
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
/// Converts all images
|
||||
pub fn convert_all(&mut self) {
|
||||
let pb = Arc::new(Mutex::new(ProgressBar::new(self.images.len() as u64)));
|
||||
pb.lock().set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("Processing images: [{bar:40.cyan/blue}]")
|
||||
.progress_chars("=> "),
|
||||
);
|
||||
self.images.par_iter().for_each(|image| {
|
||||
let mut image = image.lock();
|
||||
if let Err(e) = image.convert(self.target_format.clone(), self.target_size.clone()) {
|
||||
log::error!("Failed to embed image {:?}: {}", image.path, e)
|
||||
}
|
||||
pb.lock().tick();
|
||||
});
|
||||
pb.lock().finish_and_clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PendingImage {
|
||||
pub path: PathBuf,
|
||||
pub data: Option<Vec<u8>>,
|
||||
cache: CacheStorage,
|
||||
pub mime: Mime,
|
||||
brightness: Option<i32>,
|
||||
contrast: Option<f32>,
|
||||
huerotate: Option<i32>,
|
||||
grayscale: bool,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl PendingImage {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
let mime = get_mime(&path);
|
||||
|
||||
Self {
|
||||
path,
|
||||
data: None,
|
||||
cache: CacheStorage::new(),
|
||||
mime,
|
||||
brightness: None,
|
||||
contrast: None,
|
||||
grayscale: false,
|
||||
invert: false,
|
||||
huerotate: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_from_meta<M: Metadata>(&mut self, meta: &M) {
|
||||
if let Some(brightness) = meta.get_integer("brightness") {
|
||||
self.brightness = Some(brightness as i32);
|
||||
}
|
||||
if let Some(contrast) = meta.get_float("contrast") {
|
||||
self.contrast = Some(contrast as f32);
|
||||
}
|
||||
if let Some(huerotate) = meta.get_float("huerotate") {
|
||||
self.huerotate = Some(huerotate as i32);
|
||||
}
|
||||
self.grayscale = meta.get_bool("grayscale");
|
||||
self.invert = meta.get_bool("invert");
|
||||
}
|
||||
|
||||
/// Converts the image to the specified target format (specified by target_extension)
|
||||
pub fn convert(
|
||||
&mut self,
|
||||
target_format: Option<ImageFormat>,
|
||||
target_size: Option<(u32, u32)>,
|
||||
) -> ImageResult<()> {
|
||||
let format = target_format
|
||||
.or_else(|| {
|
||||
self.path
|
||||
.extension()
|
||||
.and_then(|extension| ImageFormat::from_extension(extension))
|
||||
})
|
||||
.unwrap_or(ImageFormat::Png);
|
||||
|
||||
let output_path = self.get_output_path(format, target_size);
|
||||
self.mime = get_mime(&output_path);
|
||||
|
||||
if self.cache.has_file(&output_path) {
|
||||
self.data = Some(self.cache.read(&output_path)?)
|
||||
} else {
|
||||
self.convert_image(format, target_size)?;
|
||||
|
||||
if let Some(data) = &self.data {
|
||||
self.cache.write(&output_path, data)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts the image
|
||||
fn convert_image(
|
||||
&mut self,
|
||||
format: ImageFormat,
|
||||
target_size: Option<(u32, u32)>,
|
||||
) -> ImageResult<()> {
|
||||
let mut image = ImageReader::open(self.get_path()?)?.decode()?;
|
||||
|
||||
if let Some((width, height)) = target_size {
|
||||
let dimensions = image.dimensions();
|
||||
|
||||
if dimensions.0 > width || dimensions.1 > height {
|
||||
image = image.resize(width, height, FilterType::Lanczos3);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(brightness) = self.brightness {
|
||||
image = image.brighten(brightness);
|
||||
}
|
||||
|
||||
if let Some(contrast) = self.contrast {
|
||||
image = image.adjust_contrast(contrast);
|
||||
}
|
||||
|
||||
if let Some(rotate) = self.huerotate {
|
||||
image = image.huerotate(rotate);
|
||||
}
|
||||
|
||||
if self.grayscale {
|
||||
image = image.grayscale();
|
||||
}
|
||||
|
||||
if self.invert {
|
||||
image.invert();
|
||||
}
|
||||
|
||||
let data = Vec::new();
|
||||
let mut writer = Cursor::new(data);
|
||||
|
||||
image.write_to(&mut writer, format)?;
|
||||
self.data = Some(writer.into_inner());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the path of the file
|
||||
fn get_path(&self) -> io::Result<PathBuf> {
|
||||
if !self.path.exists() {
|
||||
if self.cache.has_file(&self.path) {
|
||||
return Ok(self.cache.get_file_path(&self.path));
|
||||
}
|
||||
if let Some(data) = download_path(self.path.to_string_lossy().to_string()) {
|
||||
self.cache.write(&self.path, data)?;
|
||||
return Ok(self.cache.get_file_path(&self.path));
|
||||
}
|
||||
}
|
||||
Ok(self.path.clone())
|
||||
}
|
||||
|
||||
/// Returns the output file name after converting the image
|
||||
fn get_output_path(
|
||||
&self,
|
||||
target_format: ImageFormat,
|
||||
target_size: Option<(u32, u32)>,
|
||||
) -> PathBuf {
|
||||
let mut path = self.path.clone();
|
||||
let mut file_name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||
let extension = target_format.extensions_str()[0];
|
||||
let type_name = format!("{:?}", target_format);
|
||||
|
||||
if let Some(target_size) = target_size {
|
||||
file_name += &*format!("-w{}-h{}", target_size.0, target_size.1);
|
||||
}
|
||||
if let Some(b) = self.brightness {
|
||||
file_name += &*format!("-b{}", b);
|
||||
}
|
||||
if let Some(c) = self.contrast {
|
||||
file_name += &*format!("-c{}", c);
|
||||
}
|
||||
if let Some(h) = self.huerotate {
|
||||
file_name += &*format!("-h{}", h);
|
||||
}
|
||||
file_name += &*format!("{}-{}", self.invert, self.grayscale);
|
||||
|
||||
file_name += format!("-{}", type_name).as_str();
|
||||
path.set_file_name(file_name);
|
||||
path.set_extension(extension);
|
||||
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mime(path: &PathBuf) -> Mime {
|
||||
let mime = mime_guess::from_ext(
|
||||
path.clone()
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("png"),
|
||||
)
|
||||
.first()
|
||||
.unwrap_or(mime::IMAGE_PNG);
|
||||
mime
|
||||
}
|
@ -1,3 +1,11 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
pub mod caching;
|
||||
pub mod downloads;
|
||||
pub mod image_converting;
|
||||
pub mod macros;
|
||||
pub mod parsing;
|
||||
|
@ -1,6 +1,21 @@
|
||||
/*
|
||||
* Snekdown - Custom Markdown flavour and parser
|
||||
* Copyright (C) 2021 Trivernis
|
||||
* See LICENSE for more information.
|
||||
*/
|
||||
|
||||
use regex::Regex;
|
||||
#[macro_export]
|
||||
macro_rules! parse {
|
||||
($str:expr) => {
|
||||
Parser::new($str.to_string(), None).parse()
|
||||
};
|
||||
}
|
||||
|
||||
/// Removes a single backslash from the given content
|
||||
pub(crate) fn remove_single_backlslash<S: ToString>(content: S) -> String {
|
||||
let content = content.to_string();
|
||||
lazy_static::lazy_static! {static ref R: Regex = Regex::new(r"\\(?P<c>[^\\])").unwrap();}
|
||||
|
||||
R.replace_all(&*content, "$c").to_string()
|
||||
}
|
||||
|
Loading…
Reference in New Issue