Add resolution support
This commit is contained in:
parent
3f70c53aab
commit
ee9ff8e2d6
3 changed files with 434 additions and 35 deletions
76
Cargo.lock
generated
76
Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -28,13 +34,28 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-jotdown"
|
name = "cosmic-jotdown"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
|
"emath",
|
||||||
|
"image",
|
||||||
"jotdown",
|
"jotdown",
|
||||||
"log",
|
"log",
|
||||||
|
"nominals",
|
||||||
"rangemap",
|
"rangemap",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -85,6 +106,12 @@ dependencies = [
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "emath"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "font-types"
|
name = "font-types"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -117,6 +144,18 @@ dependencies = [
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.24.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jotdown"
|
name = "jotdown"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -154,19 +193,34 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "nominals"
|
||||||
version = "1.0.79"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
checksum = "bd4b6e50a0a7f2214e99ecf7f4a2c9cb9572e5817d96e37a6d31387961c23994"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.35"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -227,18 +281,18 @@ checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.198"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.197"
|
version = "1.0.198"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -273,9 +327,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.58"
|
version = "2.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -11,7 +11,9 @@ jotdown = { git = "https://git.nations.lol/fnmain/jotdown" }
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
serde = { version = "1.0.197", features = ["derive"], optional = true }
|
serde = { version = "1.0.197", features = ["derive"], optional = true }
|
||||||
rangemap = "1.5.1"
|
rangemap = "1.5.1"
|
||||||
|
nominals = "0.3.0"
|
||||||
|
emath = "0.27.2"
|
||||||
|
image = { version = "0.24.9", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["serde"]
|
|
||||||
serde = ["dep:serde", "rangemap/serde1"]
|
serde = ["dep:serde", "rangemap/serde1"]
|
||||||
|
|
389
src/lib.rs
389
src/lib.rs
|
@ -1,17 +1,21 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use cosmic_text::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, Style, Weight};
|
use cosmic_text::{self, Align};
|
||||||
use jotdown::{Container, Event, ListKind};
|
|
||||||
|
use cosmic_text::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, Style, Weight};
|
||||||
|
use emath::{Pos2, Rect, Vec2};
|
||||||
|
use jotdown::{Container, Event, ListKind, OrderedListNumbering, OrderedListStyle};
|
||||||
|
use nominals::{LetterLower, LetterUpper, Nominal, RomanLower, RomanUpper};
|
||||||
|
|
||||||
pub use jotdown;
|
|
||||||
use rangemap::RangeMap;
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde_suck;
|
pub mod serde_suck;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub use serde_suck::*;
|
pub use serde_suck::*;
|
||||||
|
|
||||||
|
use rangemap::RangeMap;
|
||||||
|
|
||||||
pub struct JotdownBufferIter<'a, T: Iterator<Item = Event<'a>>> {
|
pub struct JotdownBufferIter<'a, T: Iterator<Item = Event<'a>>> {
|
||||||
pub djot: T,
|
pub djot: T,
|
||||||
pub indent: Vec<Indent>,
|
pub indent: Vec<Indent>,
|
||||||
|
@ -27,9 +31,10 @@ struct JotdownIntoBuffer<'a, 'b, T: Iterator<Item = Event<'a>>> {
|
||||||
pub urls: Vec<(std::ops::Range<usize>, Cow<'a, str>)>,
|
pub urls: Vec<(std::ops::Range<usize>, Cow<'a, str>)>,
|
||||||
pub location: usize,
|
pub location: usize,
|
||||||
pub added: bool,
|
pub added: bool,
|
||||||
|
pub top_level_container: Option<Container<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||||
pub struct Indent {
|
pub struct Indent {
|
||||||
#[cfg_attr(feature = "serde", serde(with = "ListKindOption"))]
|
#[cfg_attr(feature = "serde", serde(with = "ListKindOption"))]
|
||||||
|
@ -37,7 +42,7 @@ pub struct Indent {
|
||||||
pub indent: f32,
|
pub indent: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const INDENT_AMOUNT: f32 = 18.0;
|
pub const INDENT_AMOUNT: f32 = 64.0;
|
||||||
|
|
||||||
impl<'a, 'b, T: Iterator<Item = Event<'a>>> Iterator for JotdownIntoBuffer<'a, 'b, T> {
|
impl<'a, 'b, T: Iterator<Item = Event<'a>>> Iterator for JotdownIntoBuffer<'a, 'b, T> {
|
||||||
type Item = (&'a str, Attrs<'static>);
|
type Item = (&'a str, Attrs<'static>);
|
||||||
|
@ -92,17 +97,32 @@ impl<'a, 'b, T: Iterator<Item = Event<'a>>> Iterator for JotdownIntoBuffer<'a, '
|
||||||
}
|
}
|
||||||
Event::Start(container, _) => match container {
|
Event::Start(container, _) => match container {
|
||||||
Container::Heading { level, .. } => {
|
Container::Heading { level, .. } => {
|
||||||
self.metrics = Metrics::new(4.0 - level as f32, 4.0 - level as f32);
|
let l = match level {
|
||||||
|
1 => 2.0,
|
||||||
|
2 => 1.5,
|
||||||
|
3 => 1.17,
|
||||||
|
4 => 1.0,
|
||||||
|
5 => 0.83,
|
||||||
|
_ => 0.67,
|
||||||
|
};
|
||||||
|
self.metrics = Metrics::new(l, l * 1.1);
|
||||||
|
self.top_level_container.get_or_insert(container);
|
||||||
}
|
}
|
||||||
Container::Emphasis => self.attrs = self.attrs.style(Style::Italic),
|
Container::Emphasis => self.attrs = self.attrs.style(Style::Italic),
|
||||||
Container::Strong => self.attrs = self.attrs.weight(Weight::BOLD),
|
Container::Strong => self.attrs = self.attrs.weight(Weight::BOLD),
|
||||||
Container::Verbatim => self.attrs = self.attrs.family(Family::Monospace),
|
Container::Verbatim => self.attrs = self.attrs.family(Family::Monospace),
|
||||||
|
Container::ListItem { .. } | Container::Paragraph => {
|
||||||
|
self.top_level_container.get_or_insert(container);
|
||||||
|
}
|
||||||
Container::List { kind, .. } => self.indent.push(Indent {
|
Container::List { kind, .. } => self.indent.push(Indent {
|
||||||
indent: self.indent.last().copied().unwrap_or_default().indent
|
indent: self.indent.last().copied().unwrap_or_default().indent
|
||||||
+ INDENT_AMOUNT,
|
+ INDENT_AMOUNT,
|
||||||
modifier: Some(kind),
|
modifier: Some(kind),
|
||||||
}),
|
}),
|
||||||
Container::Image(url, _) => self.image_url = Some(url),
|
Container::Image(ref url, _) => {
|
||||||
|
self.image_url = Some(url.clone());
|
||||||
|
self.top_level_container.get_or_insert(container);
|
||||||
|
}
|
||||||
Container::Link(_, _) => {
|
Container::Link(_, _) => {
|
||||||
self.link_start = self.location + 1;
|
self.link_start = self.location + 1;
|
||||||
self.attrs = self.attrs.color(Color::rgb(96, 198, 233));
|
self.attrs = self.attrs.color(Color::rgb(96, 198, 233));
|
||||||
|
@ -116,7 +136,7 @@ impl<'a, 'b, T: Iterator<Item = Event<'a>>> Iterator for JotdownIntoBuffer<'a, '
|
||||||
Container::List { .. } => {
|
Container::List { .. } => {
|
||||||
self.indent.pop();
|
self.indent.pop();
|
||||||
}
|
}
|
||||||
Container::Heading { .. } | Container::Paragraph | Container::Image(_, _) => {
|
Container::Heading { .. } | Container::Paragraph => {
|
||||||
if self.added {
|
if self.added {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -138,20 +158,14 @@ impl<'a, 'b, T: Iterator<Item = Event<'a>>> Iterator for JotdownIntoBuffer<'a, '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||||
pub struct RichText<'a>(
|
pub struct RichText<'a>(
|
||||||
Cow<'a, str>,
|
#[cfg_attr(feature = "serde", serde(borrow))] pub Cow<'a, str>,
|
||||||
#[cfg_attr(feature = "serde", serde(with = "AttrsSerde"))]
|
#[cfg_attr(feature = "serde", serde(with = "AttrsSerde"))] pub Attrs<'a>,
|
||||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
|
||||||
Attrs<'a>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<'a> From<(Cow<'a, str>, Attrs<'a>)> for RichText<'a> {
|
#[derive(Clone, Debug)]
|
||||||
fn from(value: (Cow<'a, str>, Attrs<'a>)) -> Self {
|
|
||||||
Self(value.0, value.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||||
pub struct JotdownItem<'a> {
|
pub struct JotdownItem<'a> {
|
||||||
pub indent: Indent,
|
pub indent: Indent,
|
||||||
|
@ -160,15 +174,235 @@ pub struct JotdownItem<'a> {
|
||||||
#[cfg_attr(feature = "serde", serde(with = "MetricsSerde"))]
|
#[cfg_attr(feature = "serde", serde(with = "MetricsSerde"))]
|
||||||
pub metrics: Metrics,
|
pub metrics: Metrics,
|
||||||
pub image_url: Option<Cow<'a, str>>,
|
pub image_url: Option<Cow<'a, str>>,
|
||||||
|
pub margin: f32,
|
||||||
pub url_map: Option<RangeMap<usize, Cow<'a, str>>>,
|
pub url_map: Option<RangeMap<usize, Cow<'a, str>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ResolvedJotdownItem<'a> {
|
||||||
|
pub indent: Indent,
|
||||||
|
pub buffer: Buffer,
|
||||||
|
pub metrics: Metrics,
|
||||||
|
pub relative_bounds: Rect,
|
||||||
|
pub url_map: Option<RangeMap<usize, Cow<'a, str>>>,
|
||||||
|
pub image_urls: Option<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_paragraphs<'a>(
|
||||||
|
paragaphs: &[JotdownItem<'a>],
|
||||||
|
viewbox: Vec2,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
metrics: Metrics,
|
||||||
|
align: Option<Align>,
|
||||||
|
factor: f32,
|
||||||
|
base_uri: &'static str,
|
||||||
|
) -> (Vec2, Vec<ResolvedJotdownItem<'a>>) {
|
||||||
|
let mut size = Vec2::new(0.0, 0.0);
|
||||||
|
// Cannot be f32::MAX
|
||||||
|
let mut last_margin = 99999.0;
|
||||||
|
let mut last_image_size: Option<Vec2> = None;
|
||||||
|
let mut first = true;
|
||||||
|
let mut in_list = false;
|
||||||
|
let mut list_number = 0;
|
||||||
|
let mut paragraphs = paragaphs
|
||||||
|
.iter()
|
||||||
|
.flat_map(|jbuffer| {
|
||||||
|
let mut buffer =
|
||||||
|
jbuffer
|
||||||
|
.clone()
|
||||||
|
.resolve(font_system, viewbox.x, metrics, align, factor);
|
||||||
|
let margin = jbuffer.margin * buffer.metrics.line_height;
|
||||||
|
|
||||||
|
let margin_top = (margin - last_margin).max(0.0);
|
||||||
|
buffer.relative_bounds = buffer
|
||||||
|
.relative_bounds
|
||||||
|
.translate(Vec2::new(buffer.indent.indent, size.y + margin_top));
|
||||||
|
|
||||||
|
let buffer_size = buffer.relative_bounds.size();
|
||||||
|
let buffer_indent = buffer.indent.indent;
|
||||||
|
let result_buffers = if let Some(url) = &jbuffer.image_url {
|
||||||
|
let url = url.split_once('#').unwrap();
|
||||||
|
let image_size = url.1.split_once('x').unwrap();
|
||||||
|
let image_size =
|
||||||
|
Vec2::new(image_size.0.parse().unwrap(), image_size.1.parse().unwrap());
|
||||||
|
let image = format!("{}/{}", base_uri, url.0);
|
||||||
|
let split = url.0.rsplit_once('.').unwrap();
|
||||||
|
let hi_image = format!("{}/{}_hi.{}", base_uri, split.0, split.1);
|
||||||
|
buffer.image_urls = Some((image, hi_image));
|
||||||
|
buffer.relative_bounds = Rect::from_min_size(
|
||||||
|
Pos2::new(buffer.indent.indent, size.y + margin_top),
|
||||||
|
image_size,
|
||||||
|
);
|
||||||
|
const IMAGE_PADDING: f32 = 8.0;
|
||||||
|
if let Some(last_size) = last_image_size.as_mut() {
|
||||||
|
let ls = *last_size;
|
||||||
|
last_size.x += image_size.x + IMAGE_PADDING;
|
||||||
|
|
||||||
|
if last_size.x > viewbox.x {
|
||||||
|
size.y += last_size.y + margin_top;
|
||||||
|
last_size.x = image_size.x + IMAGE_PADDING;
|
||||||
|
last_size.y = image_size.y + margin_top;
|
||||||
|
buffer.relative_bounds = Rect::from_min_size(
|
||||||
|
Pos2::new(buffer.indent.indent, size.y + margin_top),
|
||||||
|
image_size,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
last_size.y = last_size.y.max(image_size.y + margin_top);
|
||||||
|
buffer.relative_bounds =
|
||||||
|
buffer.relative_bounds.translate(Vec2::new(ls.x, 0.0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if image_size.x > viewbox.x {
|
||||||
|
let max_size = Vec2::new(viewbox.x, image_size.y);
|
||||||
|
let new_size = scale_to_fit(image_size, max_size, true);
|
||||||
|
buffer.relative_bounds = Rect::from_min_size(
|
||||||
|
Pos2::new(buffer.indent.indent, size.y + margin_top),
|
||||||
|
new_size,
|
||||||
|
);
|
||||||
|
size.y += new_size.y + margin_top;
|
||||||
|
} else {
|
||||||
|
last_image_size = Some(image_size + Vec2::new(IMAGE_PADDING, margin_top));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[Some(buffer), None]
|
||||||
|
} else if let Some(mut list_kind) = buffer.indent.modifier {
|
||||||
|
if let Some(image_size) = last_image_size {
|
||||||
|
size.y += image_size.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ListKind::Ordered { start, .. } = &mut list_kind {
|
||||||
|
if !in_list {
|
||||||
|
list_number = *start;
|
||||||
|
} else {
|
||||||
|
list_number += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*start = list_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_list = true;
|
||||||
|
|
||||||
|
let mut list_buffer = Buffer::new(font_system, buffer.metrics);
|
||||||
|
list_buffer.set_text(
|
||||||
|
font_system,
|
||||||
|
make_list_number(list_kind).as_ref(),
|
||||||
|
Attrs::new().family(Family::SansSerif),
|
||||||
|
Shaping::Advanced,
|
||||||
|
);
|
||||||
|
|
||||||
|
list_buffer.set_wrap(font_system, cosmic_text::Wrap::WordOrGlyph);
|
||||||
|
list_buffer.set_size(font_system, f32::MAX, f32::MAX);
|
||||||
|
|
||||||
|
list_buffer.shape_until_scroll(font_system, false);
|
||||||
|
|
||||||
|
let list_buffer_metrics = buffer.metrics;
|
||||||
|
let indent = (buffer_indent) - (INDENT_AMOUNT * factor);
|
||||||
|
[
|
||||||
|
Some(buffer),
|
||||||
|
Some(ResolvedJotdownItem {
|
||||||
|
indent: Indent {
|
||||||
|
modifier: None,
|
||||||
|
indent,
|
||||||
|
},
|
||||||
|
relative_bounds: measure_buffer(
|
||||||
|
&list_buffer,
|
||||||
|
Vec2::new(f32::MAX, f32::MAX),
|
||||||
|
)
|
||||||
|
.translate(Vec2::new(indent, size.y + margin_top)),
|
||||||
|
buffer: list_buffer,
|
||||||
|
metrics: list_buffer_metrics,
|
||||||
|
url_map: None,
|
||||||
|
image_urls: None,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
if let Some(image_size) = last_image_size {
|
||||||
|
size.y += image_size.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_list = false;
|
||||||
|
[Some(buffer), None]
|
||||||
|
};
|
||||||
|
|
||||||
|
size.y += buffer_size.y + (margin_top + margin);
|
||||||
|
last_margin = margin;
|
||||||
|
first = false;
|
||||||
|
size.x = size.x.max(buffer_size.x + buffer_indent);
|
||||||
|
|
||||||
|
result_buffers
|
||||||
|
})
|
||||||
|
.filter_map(|p| p)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
size.y -= last_margin;
|
||||||
|
|
||||||
|
if let Some(image_size) = last_image_size {
|
||||||
|
size.y += image_size.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
paragraphs.iter_mut().for_each(|paragraph| {
|
||||||
|
if paragraph.image_urls.is_none() {
|
||||||
|
paragraph
|
||||||
|
.relative_bounds
|
||||||
|
.set_width(size.x - paragraph.indent.indent);
|
||||||
|
paragraph
|
||||||
|
.buffer
|
||||||
|
.set_size(font_system, size.x - paragraph.indent.indent, f32::MAX);
|
||||||
|
paragraph.buffer.shape_until_scroll(font_system, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(size, paragraphs)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> JotdownItem<'a> {
|
||||||
|
pub fn resolve(
|
||||||
|
mut self,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
width: f32,
|
||||||
|
metrics: Metrics,
|
||||||
|
align: Option<Align>,
|
||||||
|
factor: f32,
|
||||||
|
) -> ResolvedJotdownItem<'a> {
|
||||||
|
self.indent.indent *= factor;
|
||||||
|
|
||||||
|
let buffer = self.make_buffer(font_system, width, metrics, align);
|
||||||
|
|
||||||
|
ResolvedJotdownItem {
|
||||||
|
indent: self.indent,
|
||||||
|
relative_bounds: measure_buffer(&buffer, Vec2::new(width, f32::MAX)),
|
||||||
|
buffer,
|
||||||
|
metrics: Metrics::new(
|
||||||
|
self.metrics.font_size * metrics.font_size,
|
||||||
|
self.metrics.line_height * metrics.line_height,
|
||||||
|
),
|
||||||
|
url_map: self.url_map,
|
||||||
|
image_urls: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_default(text: Vec<RichText<'a>>) -> Self {
|
||||||
|
Self {
|
||||||
|
indent: Indent {
|
||||||
|
modifier: None,
|
||||||
|
indent: 0.0,
|
||||||
|
},
|
||||||
|
buffer: text,
|
||||||
|
metrics: Metrics::new(1.0, 1.1),
|
||||||
|
url_map: None,
|
||||||
|
margin: 0.0,
|
||||||
|
image_url: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> JotdownItem<'a> {
|
impl<'a> JotdownItem<'a> {
|
||||||
pub fn make_buffer(
|
pub fn make_buffer(
|
||||||
&self,
|
&self,
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
width: f32,
|
width: f32,
|
||||||
metrics: Metrics,
|
metrics: Metrics,
|
||||||
|
align: Option<Align>,
|
||||||
) -> Buffer {
|
) -> Buffer {
|
||||||
let mut buffer = Buffer::new(
|
let mut buffer = Buffer::new(
|
||||||
font_system,
|
font_system,
|
||||||
|
@ -186,6 +420,11 @@ impl<'a> JotdownItem<'a> {
|
||||||
|
|
||||||
buffer.set_wrap(font_system, cosmic_text::Wrap::WordOrGlyph);
|
buffer.set_wrap(font_system, cosmic_text::Wrap::WordOrGlyph);
|
||||||
buffer.set_size(font_system, width - self.indent.indent, f32::MAX);
|
buffer.set_size(font_system, width - self.indent.indent, f32::MAX);
|
||||||
|
|
||||||
|
for line in &mut buffer.lines {
|
||||||
|
line.set_align(align);
|
||||||
|
}
|
||||||
|
|
||||||
buffer.shape_until_scroll(font_system, false);
|
buffer.shape_until_scroll(font_system, false);
|
||||||
buffer
|
buffer
|
||||||
}
|
}
|
||||||
|
@ -200,19 +439,21 @@ impl<'a, T: Iterator<Item = Event<'a>>> Iterator for JotdownBufferIter<'a, T> {
|
||||||
attrs: Attrs::new().family(Family::SansSerif),
|
attrs: Attrs::new().family(Family::SansSerif),
|
||||||
indent: &mut self.indent,
|
indent: &mut self.indent,
|
||||||
image_url: None,
|
image_url: None,
|
||||||
metrics: Metrics::new(1.0, 1.0),
|
metrics: Metrics::new(1.0, 1.1),
|
||||||
added: false,
|
added: false,
|
||||||
link_start: 0,
|
link_start: 0,
|
||||||
location: 0,
|
location: 0,
|
||||||
urls: Vec::new(),
|
urls: Vec::new(),
|
||||||
|
top_level_container: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let buffer = (&mut jot)
|
let buffer = (&mut jot)
|
||||||
.map(|r| RichText(Cow::Borrowed(r.0), r.1))
|
.map(|r| RichText(r.0.into(), r.1))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let image_url = jot.image_url;
|
let image_url = jot.image_url;
|
||||||
|
|
||||||
let urls = jot.urls;
|
let urls = jot.urls;
|
||||||
|
let top_level_containers = jot.top_level_container;
|
||||||
let added = jot.added;
|
let added = jot.added;
|
||||||
let metrics = jot.metrics;
|
let metrics = jot.metrics;
|
||||||
let indent = self.indent.last().copied().unwrap_or_default();
|
let indent = self.indent.last().copied().unwrap_or_default();
|
||||||
|
@ -225,12 +466,29 @@ impl<'a, T: Iterator<Item = Event<'a>>> Iterator for JotdownBufferIter<'a, T> {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let mut map = RangeMap::new();
|
let mut map = RangeMap::new();
|
||||||
map.extend(urls.into_iter());
|
map.extend(
|
||||||
|
urls.into_iter()
|
||||||
|
.map(|(range, url)| (range.start..range.end + 1, url)),
|
||||||
|
);
|
||||||
Some(map)
|
Some(map)
|
||||||
},
|
},
|
||||||
buffer,
|
buffer,
|
||||||
image_url,
|
image_url,
|
||||||
metrics,
|
metrics,
|
||||||
|
margin: match top_level_containers {
|
||||||
|
Some(Container::Heading { level, .. }) => match level {
|
||||||
|
1 => 0.34,
|
||||||
|
2 => 0.42,
|
||||||
|
3 => 0.5,
|
||||||
|
4 => 0.65,
|
||||||
|
5 => 0.85,
|
||||||
|
6 => 1.25,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Some(Container::ListItem) => 0.5,
|
||||||
|
Some(Container::Image(_, _)) => 1.0,
|
||||||
|
_ => 1.0,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,3 +502,88 @@ pub fn jotdown_into_buffers<'a, T: Iterator<Item = Event<'a>>>(
|
||||||
indent: Vec::new(),
|
indent: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_list_number(list_kind: ListKind) -> Cow<'static, str> {
|
||||||
|
match list_kind {
|
||||||
|
ListKind::Unordered | ListKind::Task => Cow::Borrowed("•"),
|
||||||
|
ListKind::Ordered {
|
||||||
|
numbering,
|
||||||
|
style,
|
||||||
|
start: number,
|
||||||
|
} => {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
if matches!(style, OrderedListStyle::ParenParen) {
|
||||||
|
result.push('(');
|
||||||
|
}
|
||||||
|
|
||||||
|
match numbering {
|
||||||
|
OrderedListNumbering::Decimal => {
|
||||||
|
result.write_fmt(format_args!("{}", number)).unwrap()
|
||||||
|
}
|
||||||
|
OrderedListNumbering::AlphaLower => {
|
||||||
|
result
|
||||||
|
.write_fmt(format_args!("{}", (number - 1).to_nominal(&LetterLower)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
OrderedListNumbering::AlphaUpper => {
|
||||||
|
result
|
||||||
|
.write_fmt(format_args!("{}", (number - 1).to_nominal(&LetterUpper)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
OrderedListNumbering::RomanLower => {
|
||||||
|
result
|
||||||
|
.write_fmt(format_args!("{}", number.to_nominal(&RomanLower)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
OrderedListNumbering::RomanUpper => {
|
||||||
|
result
|
||||||
|
.write_fmt(format_args!("{}", number.to_nominal(&RomanUpper)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match style {
|
||||||
|
OrderedListStyle::Period => result.push('.'),
|
||||||
|
OrderedListStyle::Paren | OrderedListStyle::ParenParen => result.push(')'),
|
||||||
|
}
|
||||||
|
|
||||||
|
Cow::Owned(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure_buffer(buffer: &Buffer, vb: Vec2) -> Rect {
|
||||||
|
let mut rtl = false;
|
||||||
|
let (width, total_lines) =
|
||||||
|
buffer
|
||||||
|
.layout_runs()
|
||||||
|
.fold((0.0, 0usize), |(width, total_lines), run| {
|
||||||
|
if run.rtl {
|
||||||
|
rtl = true;
|
||||||
|
}
|
||||||
|
(run.line_w.max(width), total_lines + 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
let (max_width, max_height) = buffer.size();
|
||||||
|
|
||||||
|
let size = Vec2::new(
|
||||||
|
if rtl { vb.x } else { width.min(max_width) },
|
||||||
|
(total_lines as f32 * buffer.metrics().line_height).min(max_height),
|
||||||
|
);
|
||||||
|
|
||||||
|
Rect::from_min_size(Pos2::ZERO, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_to_fit(image_size: Vec2, available_size: Vec2, maintain_aspect_ratio: bool) -> Vec2 {
|
||||||
|
if maintain_aspect_ratio {
|
||||||
|
let ratio_x = available_size.x / image_size.x;
|
||||||
|
let ratio_y = available_size.y / image_size.y;
|
||||||
|
let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y };
|
||||||
|
let ratio = if ratio.is_finite() { ratio } else { 1.0 };
|
||||||
|
image_size * ratio
|
||||||
|
} else {
|
||||||
|
available_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue