mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Remove ReX
This commit is contained in:
parent
79d851cd0b
commit
760936af49
88
Cargo.lock
generated
88
Cargo.lock
generated
@ -304,12 +304,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elsa"
|
name = "elsa"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@ -473,15 +467,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
@ -550,19 +535,6 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lexical-core"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
|
||||||
dependencies = [
|
|
||||||
"arrayvec 0.5.2",
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"ryu",
|
|
||||||
"static_assertions",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.139"
|
||||||
@ -647,17 +619,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "5.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
|
||||||
dependencies = [
|
|
||||||
"lexical-core",
|
|
||||||
"memchr",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify"
|
name = "notify"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@ -732,9 +693,9 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "pixglyph"
|
name = "pixglyph"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/typst/pixglyph#131ea492ceada591c7e2d6dd2a5958ae325bd0d4"
|
source = "git+https://github.com/typst/pixglyph#e3ff0272d6723cdada91a00b0c99cda0e5adb56d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ttf-parser 0.17.1",
|
"ttf-parser 0.18.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -864,17 +825,6 @@ dependencies = [
|
|||||||
"usvg",
|
"usvg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rex"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "git+https://github.com/laurmaedje/ReX#523b29bd39a4daf50f9bb3ee48689c66b209c4ff"
|
|
||||||
dependencies = [
|
|
||||||
"itertools",
|
|
||||||
"nom",
|
|
||||||
"ttf-parser 0.17.1",
|
|
||||||
"unicode-math",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "rgb"
|
||||||
version = "0.8.34"
|
version = "0.8.34"
|
||||||
@ -997,12 +947,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "static_assertions"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subsetter"
|
name = "subsetter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1128,6 +1072,12 @@ version = "0.17.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff"
|
checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-arena"
|
name = "typed-arena"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@ -1150,7 +1100,6 @@ dependencies = [
|
|||||||
"pixglyph",
|
"pixglyph",
|
||||||
"regex",
|
"regex",
|
||||||
"resvg",
|
"resvg",
|
||||||
"rex",
|
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1160,8 +1109,9 @@ dependencies = [
|
|||||||
"symmie",
|
"symmie",
|
||||||
"thin-vec",
|
"thin-vec",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
"ttf-parser 0.17.1",
|
"ttf-parser 0.18.1",
|
||||||
"typst-macros",
|
"typst-macros",
|
||||||
|
"unicode-math-class",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
"unscanny",
|
"unscanny",
|
||||||
@ -1198,17 +1148,16 @@ dependencies = [
|
|||||||
"kurbo",
|
"kurbo",
|
||||||
"lipsum",
|
"lipsum",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rex",
|
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"symmie",
|
"symmie",
|
||||||
"syntect",
|
"syntect",
|
||||||
"ttf-parser 0.17.1",
|
"ttf-parser 0.18.1",
|
||||||
"typed-arena",
|
"typed-arena",
|
||||||
"typst",
|
"typst",
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
"unicode-math",
|
"unicode-math-class",
|
||||||
"unicode-script",
|
"unicode-script",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"xi-unicode",
|
"xi-unicode",
|
||||||
@ -1271,12 +1220,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-math"
|
name = "unicode-math-class"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/s3bk/unicode-math/#8ab17ee2b125747876f48dfb579d58482bf876e5"
|
source = "git+https://github.com/typst/unicode-math-class#a7ac7dd75cd79ab2e0bdb629036cb913371608d2"
|
||||||
dependencies = [
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-script"
|
name = "unicode-script"
|
||||||
@ -1328,12 +1274,6 @@ dependencies = [
|
|||||||
"svgtypes",
|
"svgtypes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
@ -25,7 +25,6 @@ pdf-writer = "0.6"
|
|||||||
pixglyph = { git = "https://github.com/typst/pixglyph" }
|
pixglyph = { git = "https://github.com/typst/pixglyph" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
resvg = { version = "0.22", default-features = false }
|
resvg = { version = "0.22", default-features = false }
|
||||||
rex = { git = "https://github.com/laurmaedje/ReX" }
|
|
||||||
roxmltree = "0.14"
|
roxmltree = "0.14"
|
||||||
rustybuzz = "0.5"
|
rustybuzz = "0.5"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@ -35,7 +34,8 @@ svg2pdf = "0.4"
|
|||||||
symmie = { git = "https://github.com/typst/symmie" }
|
symmie = { git = "https://github.com/typst/symmie" }
|
||||||
thin-vec = "0.2"
|
thin-vec = "0.2"
|
||||||
tiny-skia = "0.6.2"
|
tiny-skia = "0.6.2"
|
||||||
ttf-parser = "0.17"
|
ttf-parser = "0.18.1"
|
||||||
|
unicode-math-class = { git = "https://github.com/typst/unicode-math-class" }
|
||||||
unicode-segmentation = "1"
|
unicode-segmentation = "1"
|
||||||
unicode-xid = "0.2"
|
unicode-xid = "0.2"
|
||||||
unscanny = "0.1"
|
unscanny = "0.1"
|
||||||
|
@ -17,16 +17,15 @@ hypher = "0.1"
|
|||||||
kurbo = "0.8"
|
kurbo = "0.8"
|
||||||
lipsum = { git = "https://github.com/reknih/lipsum" }
|
lipsum = { git = "https://github.com/reknih/lipsum" }
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
rex = { git = "https://github.com/laurmaedje/ReX" }
|
|
||||||
roxmltree = "0.14"
|
roxmltree = "0.14"
|
||||||
rustybuzz = "0.5"
|
rustybuzz = "0.5"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
symmie = { git = "https://github.com/typst/symmie" }
|
symmie = { git = "https://github.com/typst/symmie" }
|
||||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||||
ttf-parser = "0.17"
|
ttf-parser = "0.18.1"
|
||||||
typed-arena = "2"
|
typed-arena = "2"
|
||||||
unicode-bidi = "0.3.5"
|
unicode-bidi = "0.3.5"
|
||||||
unicode-math = { git = "https://github.com/s3bk/unicode-math/" }
|
unicode-math-class = { git = "https://github.com/typst/unicode-math-class" }
|
||||||
unicode-script = "0.5"
|
unicode-script = "0.5"
|
||||||
unicode-segmentation = "1"
|
unicode-segmentation = "1"
|
||||||
xi-unicode = "0.3"
|
xi-unicode = "0.3"
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
use rex::error::{Error, LayoutError};
|
|
||||||
use rex::font::FontContext;
|
|
||||||
use rex::layout::{LayoutSettings, Style};
|
|
||||||
use rex::parser::color::RGBA;
|
|
||||||
use rex::render::{Backend, Cursor, Renderer};
|
|
||||||
use typst::font::Font;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::text::{families, variant, TextNode};
|
|
||||||
|
|
||||||
/// Layout a TeX formula into a frame.
|
|
||||||
pub fn layout_tex(
|
|
||||||
vt: &Vt,
|
|
||||||
tex: &str,
|
|
||||||
display: bool,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> Result<Fragment, &'static str> {
|
|
||||||
// Load the font.
|
|
||||||
let variant = variant(styles);
|
|
||||||
let world = vt.world();
|
|
||||||
let mut font = None;
|
|
||||||
for family in families(styles) {
|
|
||||||
font = world.book().select(family, variant).and_then(|id| world.font(id));
|
|
||||||
if font.as_ref().map_or(false, |font| font.math().is_some()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the font context.
|
|
||||||
let Some(font) = font else { return Err("failed to find suitable math font") };
|
|
||||||
let Some(ctx) = font
|
|
||||||
.math()
|
|
||||||
.map(|math| FontContext::new(font.ttf(), math))
|
|
||||||
else {
|
|
||||||
return Err("failed to create math font context");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Layout the formula.
|
|
||||||
let em = styles.get(TextNode::SIZE);
|
|
||||||
let style = if display { Style::Display } else { Style::Text };
|
|
||||||
let settings = LayoutSettings::new(&ctx, em.to_pt(), style);
|
|
||||||
let renderer = Renderer::new();
|
|
||||||
let Ok(layout) = renderer
|
|
||||||
.layout(&tex, settings)
|
|
||||||
.map_err(|err| match err {
|
|
||||||
Error::Parse(err) => err.to_string(),
|
|
||||||
Error::Layout(LayoutError::Font(err)) => err.to_string(),
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
return Err("failed to layout math");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine the metrics.
|
|
||||||
let (x0, y0, x1, y1) = renderer.size(&layout);
|
|
||||||
let width = Abs::pt(x1 - x0);
|
|
||||||
let mut top = Abs::pt(y1);
|
|
||||||
let mut bottom = Abs::pt(-y0);
|
|
||||||
if style != Style::Display {
|
|
||||||
let metrics = font.metrics();
|
|
||||||
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
|
|
||||||
bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prepare a frame rendering backend.
|
|
||||||
let size = Size::new(width, top + bottom);
|
|
||||||
let mut backend = FrameBackend {
|
|
||||||
frame: {
|
|
||||||
let mut frame = Frame::new(size);
|
|
||||||
frame.set_baseline(top);
|
|
||||||
frame
|
|
||||||
},
|
|
||||||
baseline: top,
|
|
||||||
font: font.clone(),
|
|
||||||
paint: styles.get(TextNode::FILL),
|
|
||||||
lang: styles.get(TextNode::LANG),
|
|
||||||
colors: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render into the frame.
|
|
||||||
renderer.render(&layout, &mut backend);
|
|
||||||
|
|
||||||
Ok(Fragment::frame(backend.frame))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A ReX rendering backend that renders into a frame.
|
|
||||||
struct FrameBackend {
|
|
||||||
frame: Frame,
|
|
||||||
baseline: Abs,
|
|
||||||
font: Font,
|
|
||||||
paint: Paint,
|
|
||||||
lang: Lang,
|
|
||||||
colors: Vec<RGBA>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameBackend {
|
|
||||||
/// The currently active paint.
|
|
||||||
fn paint(&self) -> Paint {
|
|
||||||
self.colors
|
|
||||||
.last()
|
|
||||||
.map(|&RGBA(r, g, b, a)| RgbaColor::new(r, g, b, a).into())
|
|
||||||
.unwrap_or(self.paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a cursor to a point.
|
|
||||||
fn transform(&self, cursor: Cursor) -> Point {
|
|
||||||
Point::new(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Backend for FrameBackend {
|
|
||||||
fn symbol(&mut self, pos: Cursor, gid: u16, scale: f64) {
|
|
||||||
self.frame.push(
|
|
||||||
self.transform(pos),
|
|
||||||
Element::Text(Text {
|
|
||||||
font: self.font.clone(),
|
|
||||||
size: Abs::pt(scale),
|
|
||||||
fill: self.paint(),
|
|
||||||
lang: self.lang,
|
|
||||||
glyphs: vec![Glyph {
|
|
||||||
id: gid,
|
|
||||||
x_advance: Em::new(0.0),
|
|
||||||
x_offset: Em::new(0.0),
|
|
||||||
c: ' ',
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rule(&mut self, pos: Cursor, width: f64, height: f64) {
|
|
||||||
if height == 0.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.frame.push(
|
|
||||||
self.transform(pos) + Point::with_y(Abs::pt(height) / 2.0),
|
|
||||||
Element::Shape(Shape {
|
|
||||||
geometry: Geometry::Line(Point::new(Abs::pt(width), Abs::zero())),
|
|
||||||
fill: None,
|
|
||||||
stroke: Some(Stroke { paint: self.paint(), thickness: Abs::pt(height) }),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_color(&mut self, color: RGBA) {
|
|
||||||
self.colors.push(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end_color(&mut self) {
|
|
||||||
self.colors.pop();
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,9 +10,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use once_cell::unsync::OnceCell;
|
use ttf_parser::GlyphId;
|
||||||
use rex::font::MathHeader;
|
|
||||||
use ttf_parser::{GlyphId, Tag};
|
|
||||||
|
|
||||||
use crate::geom::Em;
|
use crate::geom::Em;
|
||||||
use crate::util::Buffer;
|
use crate::util::Buffer;
|
||||||
@ -37,8 +35,6 @@ struct Repr {
|
|||||||
ttf: ttf_parser::Face<'static>,
|
ttf: ttf_parser::Face<'static>,
|
||||||
/// The underlying rustybuzz face.
|
/// The underlying rustybuzz face.
|
||||||
rusty: rustybuzz::Face<'static>,
|
rusty: rustybuzz::Face<'static>,
|
||||||
/// The parsed ReX math header.
|
|
||||||
math: OnceCell<Option<MathHeader>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
@ -58,15 +54,7 @@ impl Font {
|
|||||||
let metrics = FontMetrics::from_ttf(&ttf);
|
let metrics = FontMetrics::from_ttf(&ttf);
|
||||||
let info = FontInfo::from_ttf(&ttf)?;
|
let info = FontInfo::from_ttf(&ttf)?;
|
||||||
|
|
||||||
Some(Self(Arc::new(Repr {
|
Some(Self(Arc::new(Repr { data, index, info, metrics, ttf, rusty })))
|
||||||
data,
|
|
||||||
index,
|
|
||||||
info,
|
|
||||||
metrics,
|
|
||||||
ttf,
|
|
||||||
rusty,
|
|
||||||
math: OnceCell::new(),
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse all fonts in the given data.
|
/// Parse all fonts in the given data.
|
||||||
@ -131,17 +119,6 @@ impl Font {
|
|||||||
// internal 'static lifetime.
|
// internal 'static lifetime.
|
||||||
&self.0.rusty
|
&self.0.rusty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the math header, if any.
|
|
||||||
pub fn math(&self) -> Option<&MathHeader> {
|
|
||||||
self.0
|
|
||||||
.math
|
|
||||||
.get_or_init(|| {
|
|
||||||
let data = self.ttf().raw_face().table(Tag::from_bytes(b"MATH"))?;
|
|
||||||
MathHeader::parse(data).ok()
|
|
||||||
})
|
|
||||||
.as_ref()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Font {
|
impl Hash for Font {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user