commit f7b7310259deeb9949604e72b54f850419939c7f Author: Isaac Mills Date: Mon Mar 18 18:33:03 2024 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..88bfd79 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,370 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bytemuck" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" + +[[package]] +name = "cosmic-jotdown" +version = "0.1.0" +dependencies = [ + "cosmic-text", + "jotdown", + "log", + "range-map", +] + +[[package]] +name = "cosmic-text" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c578f2b9abb4d5f3fbb12aba4008084d435dc6a8425c195cfe0b3594bfea0c25" +dependencies = [ + "bitflags", + "fontdb", + "libm", + "log", + "rangemap", + "rustc-hash", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "ttf-parser", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "databake" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82175d72e69414ceafbe2b49686794d3a8bed846e0d50267355f83ea8fdd953a" +dependencies = [ + "databake-derive", + "proc-macro2", + "quote", +] + +[[package]] +name = "databake-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "font-types" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7f6040d337bd44434ab21fc6509154edf2cece88b23758d9d64654c4e7730b" + +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "jotdown" +version = "0.3.2" +dependencies = [ + "databake", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[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.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "range-map" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + +[[package]] +name = "read-fonts" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c524658d3b77930a391f559756d91dbe829ab6cf4687083f615d395df99722" +dependencies = [ + "font-types", +] + +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustybuzz" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" +dependencies = [ + "bitflags", + "bytemuck", + "libm", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "swash" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af636fb90d39858650cae1088a37e2862dab4e874a0bb49d6dfb5b2dacf0e24" +dependencies = [ + "read-fonts", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sys-locale" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +dependencies = [ + "libc", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..38586ca --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cosmic-jotdown" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmic-text = "0.11.2" +jotdown = { version = "0.3.2", path = "../jotdown" } +log = "0.4.21" +range-map = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..90dcb61 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,237 @@ +use std::borrow::Cow; + +use cosmic_text::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, Style, Weight}; +use jotdown::{Container, Event, ListKind}; + +pub use jotdown; +use range_map::RangeMap; + +pub struct JotdownBufferIter<'a, 'b, T: Iterator>> { + djot: T, + font_system: &'b mut FontSystem, + width: f32, + metrics: Metrics, + indent: Vec, +} + +struct JotdownIntoBuffer<'a, 'b, T: Iterator>> { + djot: &'b mut T, + attrs: Attrs<'a>, + metrics: Metrics, + indent: &'b mut Vec, + image_url: Option>, + link_start: usize, + urls: Vec<(range_map::Range, &'a str)>, + location: usize, + added: bool, +} + +#[derive(Default, Clone, Copy)] +pub struct Indent { + pub modifier: Option, + pub indent: f32, +} + +pub const INDENT_AMOUNT: f32 = 18.0; + +impl<'a, 'b, T: Iterator>> Iterator for JotdownIntoBuffer<'a, 'b, T> { + type Item = (&'a str, Attrs<'a>); + + fn next(&mut self) -> Option { + while let Some(event) = self.djot.next() { + match event { + Event::LeftSingleQuote => { + self.added = true; + self.location += "‘".len(); + return Some(("‘", self.attrs.clone())); + } + Event::LeftDoubleQuote => { + self.added = true; + self.location += "“".len(); + return Some(("“", self.attrs.clone())); + } + Event::RightSingleQuote => { + self.added = true; + self.location += "’".len(); + return Some(("’", self.attrs.clone())); + } + Event::RightDoubleQuote => { + self.added = true; + self.location += "”".len(); + return Some(("”", self.attrs.clone())); + } + Event::Ellipsis => { + self.added = true; + self.location += "…".len(); + return Some(("…", self.attrs.clone())); + } + Event::EmDash => { + self.added = true; + self.location += "—".len(); + return Some(("—", self.attrs.clone())); + } + Event::EnDash => { + self.added = true; + self.location += "–".len(); + return Some(("–", self.attrs.clone())); + } + Event::Softbreak | Event::NonBreakingSpace => { + self.added = true; + self.location += " ".len(); + return Some((" ", self.attrs.clone())); + } + Event::Str(Cow::Borrowed(s)) | Event::Symbol(Cow::Borrowed(s)) => { + self.added = true; + self.location += s.len(); + return Some((s, self.attrs.clone())); + } + Event::Start(container, _) => match container { + Container::Heading { level, .. } => { + self.metrics = Metrics::new( + self.metrics.font_size * (4.0 - level as f32), + self.metrics.line_height * (4.0 - level as f32), + ); + } + Container::Emphasis => self.attrs = self.attrs.style(Style::Italic), + Container::Strong => self.attrs = self.attrs.weight(Weight::BOLD), + Container::Verbatim => self.attrs = self.attrs.family(Family::Monospace), + Container::List { kind, .. } => self.indent.push(Indent { + indent: self.indent.last().copied().unwrap_or_default().indent + + INDENT_AMOUNT, + modifier: Some(kind), + }), + Container::Image(url, _) => self.image_url = Some(url), + Container::Link(_, _) => { + self.link_start = self.location + 1; + self.attrs = self.attrs.color(Color::rgb(96, 198, 233)); + } + _ => {} + }, + Event::End(container) => match container { + Container::Emphasis => self.attrs = self.attrs.style(Style::Normal), + Container::Strong => self.attrs = self.attrs.weight(Weight::NORMAL), + Container::Verbatim => self.attrs = self.attrs.family(Family::SansSerif), + Container::List { .. } => { + self.indent.pop(); + } + Container::Heading { .. } | Container::Paragraph | Container::Image(_, _) => { + if self.added { + return None; + } + } + Container::Link(Cow::Borrowed(url), _) => { + self.urls + .push((range_map::Range::new(self.link_start, self.location), url)); + self.attrs = self.attrs.color(Color::rgb(255, 255, 255)); + } + _ => {} + }, + Event::Hardbreak | Event::ThematicBreak(_) => {} + Event::Blankline | Event::Escape | Event::FootnoteReference(_) => {} + Event::Str(Cow::Owned(_)) | Event::Symbol(Cow::Owned(_)) => panic!(), + } + } + + return None; + } +} + +pub struct JotdownItem<'a> { + pub indent: Indent, + pub buffer: Buffer, + pub image_url: Option>, + pub url_map: Option>, +} + +impl<'a, 'b, T: Iterator>> Iterator for JotdownBufferIter<'a, 'b, T> { + type Item = JotdownItem<'a>; + + fn next(&mut self) -> Option { + let mut buffer = Buffer::new(&mut self.font_system, self.metrics); + + let mut jot = JotdownIntoBuffer { + djot: &mut self.djot, + attrs: Attrs::new().family(Family::SansSerif), + metrics: self.metrics, + indent: &mut self.indent, + image_url: None, + added: false, + link_start: 0, + location: 0, + urls: Vec::new(), + }; + + buffer.set_rich_text( + &mut self.font_system, + &mut jot, + Attrs::new().family(Family::SansSerif), + Shaping::Advanced, + ); + + buffer.set_wrap(&mut self.font_system, cosmic_text::Wrap::WordOrGlyph); + buffer.set_metrics(&mut self.font_system, jot.metrics); + let image_url = jot.image_url; + let urls = jot.urls; + let added = jot.added; + let indent = self.indent.last().copied().unwrap_or_default(); + buffer.set_size(&mut self.font_system, self.width - indent.indent, f32::MAX); + + if !added { + return None; + } else { + buffer.shape_until_scroll(&mut self.font_system, true); + return Some(JotdownItem { + indent, + url_map: if urls.is_empty() { + None + } else { + RangeMap::try_from_iter( + urls.into_iter(), // .scan( + // ( + // 0, + // buffer + // .layout_runs() + // .flat_map(|run| run.glyphs.into_iter()) + // .enumerate(), + // ), + // |(len, glyphs), range| { + // let mut start = None; + // while let Some(glyph) = glyphs.next() { + // *len += glyph.1.end - glyph.1.start; + // if start.is_none() && *len >= range.0.start { + // start = Some(glyph.0); + // } else if *len >= range.0.end { + // return Some(( + // range_map::Range::new(start.unwrap(), glyph.0), + // range.1, + // )); + // } + // } + // log::info!("{:?}", start); + // return None; + // }, + // ), + ) + .ok() + }, + buffer, + image_url, + }); + } + } +} + +pub fn jotdown_into_buffers<'a, 'b, T: Iterator>>( + djot: T, + font_system: &'b mut FontSystem, + metrics: Metrics, + width: f32, +) -> JotdownBufferIter<'a, 'b, T> { + JotdownBufferIter { + djot, + font_system, + width, + metrics, + indent: Vec::new(), + } +}