mirror of
https://github.com/typst/typst
synced 2025-08-24 03:34:14 +08:00
feat: use LoadError to avoid polluting memoization with callsite spans and file ids
This commit is contained in:
parent
7933ccb961
commit
1c4eea8353
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3039,6 +3039,7 @@ dependencies = [
|
|||||||
"icu_provider_blob",
|
"icu_provider_blob",
|
||||||
"icu_segmenter",
|
"icu_segmenter",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
|
"memchr",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
|
@ -30,6 +30,7 @@ icu_provider_adapters = { workspace = true }
|
|||||||
icu_provider_blob = { workspace = true }
|
icu_provider_blob = { workspace = true }
|
||||||
icu_segmenter = { workspace = true }
|
icu_segmenter = { workspace = true }
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
|
memchr = { workspace = true }
|
||||||
rustybuzz = { workspace = true }
|
rustybuzz = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
use typst_library::diag::{warning, At, SourceResult, StrResult};
|
use typst_library::diag::{warning, At, LoadedAt, SourceResult, StrResult};
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Bytes, Derived, Packed, Smart, StyleChain};
|
use typst_library::foundations::{Bytes, Derived, Packed, Smart, StyleChain};
|
||||||
use typst_library::introspection::Locator;
|
use typst_library::introspection::Locator;
|
||||||
@ -30,14 +30,14 @@ pub fn layout_image(
|
|||||||
let Derived { source, derived: data } = &elem.source;
|
let Derived { source, derived: data } = &elem.source;
|
||||||
let format = match elem.format(styles) {
|
let format = match elem.format(styles) {
|
||||||
Smart::Custom(v) => v,
|
Smart::Custom(v) => v,
|
||||||
Smart::Auto => determine_format(source, data).at(span)?,
|
Smart::Auto => determine_format(source, &data.bytes).at(span)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Warn the user if the image contains a foreign object. Not perfect
|
// Warn the user if the image contains a foreign object. Not perfect
|
||||||
// because the svg could also be encoded, but that's an edge case.
|
// because the svg could also be encoded, but that's an edge case.
|
||||||
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
||||||
let has_foreign_object =
|
let has_foreign_object =
|
||||||
data.as_str().is_ok_and(|s| s.contains("<foreignObject"));
|
memchr::memmem::find(&data.bytes, b"<foreignObject").is_some();
|
||||||
|
|
||||||
if has_foreign_object {
|
if has_foreign_object {
|
||||||
engine.sink.warn(warning!(
|
engine.sink.warn(warning!(
|
||||||
@ -53,7 +53,7 @@ pub fn layout_image(
|
|||||||
let kind = match format {
|
let kind = match format {
|
||||||
ImageFormat::Raster(format) => ImageKind::Raster(
|
ImageFormat::Raster(format) => ImageKind::Raster(
|
||||||
RasterImage::new(
|
RasterImage::new(
|
||||||
data.clone(),
|
data.bytes.clone(),
|
||||||
format,
|
format,
|
||||||
elem.icc(styles).as_ref().map(|icc| icc.derived.clone()),
|
elem.icc(styles).as_ref().map(|icc| icc.derived.clone()),
|
||||||
)
|
)
|
||||||
@ -61,11 +61,11 @@ pub fn layout_image(
|
|||||||
),
|
),
|
||||||
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
||||||
SvgImage::with_fonts(
|
SvgImage::with_fonts(
|
||||||
data.clone(),
|
data.bytes.clone(),
|
||||||
engine.world,
|
engine.world,
|
||||||
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.at(span)?,
|
.in_text(&data)?,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -570,8 +570,54 @@ impl From<PackageError> for EcoString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loaded {
|
pub type LoadResult<T> = Result<T, LoadError>;
|
||||||
|
|
||||||
|
/// A callsite independent error that occurred during data loading.
|
||||||
|
/// Can be turned into a [`SourceDiagnostic`] using the [`LoadedAt::in_text`]
|
||||||
|
/// or [`LoadedAt::in_invalid_text`] methods available on [`LoadResult`].
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct LoadError {
|
||||||
|
pub pos: ReportPos,
|
||||||
|
pub message: EcoString,
|
||||||
|
pub error: EcoString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoadError {
|
||||||
|
pub fn new(
|
||||||
|
pos: impl Into<ReportPos>,
|
||||||
|
message: impl std::fmt::Display,
|
||||||
|
error: impl std::fmt::Display,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.into(),
|
||||||
|
message: eco_format!("{message}"),
|
||||||
|
error: eco_format!("{error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a [`LoadResult`] to a [`SourceResult`] by adding the [`Loaded`] context.
|
||||||
|
pub trait LoadedAt<T> {
|
||||||
|
/// Add the span information.
|
||||||
|
fn in_text(self, data: &Loaded) -> SourceResult<T>;
|
||||||
|
|
||||||
|
/// Add the span information.
|
||||||
|
fn in_invalid_text(self, data: &Loaded) -> SourceResult<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LoadedAt<T> for Result<T, LoadError> {
|
||||||
/// Report an error, possibly in an external file.
|
/// Report an error, possibly in an external file.
|
||||||
|
fn in_text(self, data: &Loaded) -> SourceResult<T> {
|
||||||
|
self.map_err(|err| data.err_in_text(err.pos, err.message, err.error))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Report an error in invalid text.
|
||||||
|
fn in_invalid_text(self, data: &Loaded) -> SourceResult<T> {
|
||||||
|
self.map_err(|err| data.err_in_invalid_text(err.pos, err.message, err.error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loaded {
|
||||||
pub fn err_in_text(
|
pub fn err_in_text(
|
||||||
&self,
|
&self,
|
||||||
pos: impl Into<ReportPos>,
|
pos: impl Into<ReportPos>,
|
||||||
@ -632,7 +678,7 @@ impl Loaded {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
pub enum ReportPos {
|
pub enum ReportPos {
|
||||||
/// Contains the range, and the 0-based line/column.
|
/// Contains the range, and the 0-based line/column.
|
||||||
Full(std::ops::Range<usize>, LineCol),
|
Full(std::ops::Range<usize>, LineCol),
|
||||||
@ -694,7 +740,7 @@ impl ReportPos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A line/column pair.
|
/// A line/column pair.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct LineCol {
|
pub struct LineCol {
|
||||||
/// The 0-based line.
|
/// The 0-based line.
|
||||||
line: usize,
|
line: usize,
|
||||||
@ -732,38 +778,34 @@ impl LineCol {
|
|||||||
|
|
||||||
/// Returns the 0-based line/column indices.
|
/// Returns the 0-based line/column indices.
|
||||||
pub fn indices(&self) -> (usize, usize) {
|
pub fn indices(&self) -> (usize, usize) {
|
||||||
(self.line, self.col)
|
(self.line as usize, self.col as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the 1-based line/column numbers.
|
/// Returns the 1-based line/column numbers.
|
||||||
pub fn numbers(&self) -> (usize, usize) {
|
pub fn numbers(&self) -> (usize, usize) {
|
||||||
(self.line + 1, self.col + 1)
|
(self.line as usize + 1, self.col as usize + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a user-facing error message for an XML-like file format.
|
/// Format a user-facing error message for an XML-like file format.
|
||||||
pub fn format_xml_like_error(
|
pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> LoadError {
|
||||||
format: &str,
|
|
||||||
data: &Loaded,
|
|
||||||
error: roxmltree::Error,
|
|
||||||
) -> EcoVec<SourceDiagnostic> {
|
|
||||||
let pos = LineCol::one_based(error.pos().row as usize, error.pos().col as usize);
|
let pos = LineCol::one_based(error.pos().row as usize, error.pos().col as usize);
|
||||||
let msg = format!("failed to parse {format}");
|
let msg = eco_format!("failed to parse {format}");
|
||||||
let err = match error {
|
let err = match error {
|
||||||
roxmltree::Error::UnexpectedCloseTag(expected, actual, _) => {
|
roxmltree::Error::UnexpectedCloseTag(expected, actual, _) => {
|
||||||
format!("found closing tag '{actual}' instead of '{expected}'")
|
eco_format!("found closing tag '{actual}' instead of '{expected}'")
|
||||||
}
|
}
|
||||||
roxmltree::Error::UnknownEntityReference(entity, _) => {
|
roxmltree::Error::UnknownEntityReference(entity, _) => {
|
||||||
format!("unknown entity '{entity}'")
|
eco_format!("unknown entity '{entity}'")
|
||||||
}
|
}
|
||||||
roxmltree::Error::DuplicatedAttribute(attr, _) => {
|
roxmltree::Error::DuplicatedAttribute(attr, _) => {
|
||||||
format!("duplicate attribute '{attr}'")
|
eco_format!("duplicate attribute '{attr}'")
|
||||||
}
|
}
|
||||||
roxmltree::Error::NoRootNode => {
|
roxmltree::Error::NoRootNode => {
|
||||||
format!("missing root node")
|
eco_format!("missing root node")
|
||||||
}
|
}
|
||||||
err => err.to_string(),
|
err => eco_format!("{err}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
data.err_in_text(pos, msg, err)
|
LoadError::new(pos, msg, err)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ mod yaml_;
|
|||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst_syntax::{FileId, Span, Spanned};
|
use typst_syntax::{FileId, Spanned};
|
||||||
|
|
||||||
pub use self::cbor_::*;
|
pub use self::cbor_::*;
|
||||||
pub use self::csv_::*;
|
pub use self::csv_::*;
|
||||||
@ -27,7 +27,7 @@ pub use self::toml_::*;
|
|||||||
pub use self::xml_::*;
|
pub use self::xml_::*;
|
||||||
pub use self::yaml_::*;
|
pub use self::yaml_::*;
|
||||||
|
|
||||||
use crate::diag::{At, FileError, SourceResult};
|
use crate::diag::{At, LoadError, LoadResult, LoadedAt, SourceResult};
|
||||||
use crate::foundations::OneOrMultiple;
|
use crate::foundations::OneOrMultiple;
|
||||||
use crate::foundations::{cast, Bytes, Scope, Str};
|
use crate::foundations::{cast, Bytes, Scope, Str};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -121,46 +121,47 @@ impl Load for Spanned<&OneOrMultiple<DataSource>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Data loaded from a [`DataSource`].
|
/// Data loaded from a [`DataSource`].
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Loaded {
|
pub struct Loaded {
|
||||||
pub source: Spanned<LoadSource>,
|
pub source: Spanned<LoadSource>,
|
||||||
pub bytes: Bytes,
|
pub bytes: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loaded {
|
impl Loaded {
|
||||||
/// FIXME: remove this?
|
|
||||||
pub fn dummy() -> Self {
|
|
||||||
Loaded::new(
|
|
||||||
typst_syntax::Spanned::new(LoadSource::Bytes, Span::detached()),
|
|
||||||
Bytes::new([]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(source: Spanned<LoadSource>, bytes: Bytes) -> Self {
|
pub fn new(source: Spanned<LoadSource>, bytes: Bytes) -> Self {
|
||||||
Self { source, bytes }
|
Self { source, bytes }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> SourceResult<&str> {
|
pub fn load_str(&self) -> SourceResult<&str> {
|
||||||
self.bytes.as_str().map_err(|err| {
|
self.bytes.load_str().in_invalid_text(self)
|
||||||
let start = err.valid_up_to();
|
|
||||||
let end = start + err.error_len().unwrap_or(0);
|
|
||||||
// always report this error in the source file.
|
|
||||||
self.err_in_invalid_text(
|
|
||||||
start..end,
|
|
||||||
"failed to convert to string",
|
|
||||||
FileError::from(err),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A loaded [`DataSource`].
|
/// A loaded [`DataSource`].
|
||||||
#[derive(Clone, Copy, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum LoadSource {
|
pub enum LoadSource {
|
||||||
Path(FileId),
|
Path(FileId),
|
||||||
Bytes,
|
Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait LoadStr {
|
||||||
|
fn load_str(&self) -> LoadResult<&str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> LoadStr for T {
|
||||||
|
fn load_str(&self) -> LoadResult<&str> {
|
||||||
|
std::str::from_utf8(self.as_ref()).map_err(|err| {
|
||||||
|
let start = err.valid_up_to();
|
||||||
|
let end = start + err.error_len().unwrap_or(0);
|
||||||
|
LoadError::new(
|
||||||
|
start..end,
|
||||||
|
"failed to convert to string",
|
||||||
|
"file is not valid utf-8",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A value that can be read from a file.
|
/// A value that can be read from a file.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub enum Readable {
|
pub enum Readable {
|
||||||
|
@ -38,7 +38,7 @@ pub fn read(
|
|||||||
let data = path.map(DataSource::Path).load(engine.world)?;
|
let data = path.map(DataSource::Path).load(engine.world)?;
|
||||||
Ok(match encoding {
|
Ok(match encoding {
|
||||||
None => Readable::Bytes(data.bytes),
|
None => Readable::Bytes(data.bytes),
|
||||||
Some(Encoding::Utf8) => Readable::Str(data.as_str()?.into()),
|
Some(Encoding::Utf8) => Readable::Str(data.load_str()?.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use ecow::{eco_format, EcoVec};
|
use ecow::eco_format;
|
||||||
use typst_syntax::Spanned;
|
use typst_syntax::Spanned;
|
||||||
|
|
||||||
use crate::diag::{At, ReportPos, SourceDiagnostic, SourceResult};
|
use crate::diag::{At, LoadError, LoadedAt, ReportPos, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{func, scope, Str, Value};
|
use crate::foundations::{func, scope, Str, Value};
|
||||||
use crate::loading::{DataSource, Load, Loaded, Readable};
|
use crate::loading::{DataSource, Load, Readable};
|
||||||
|
|
||||||
/// Reads structured data from a TOML file.
|
/// Reads structured data from a TOML file.
|
||||||
///
|
///
|
||||||
@ -33,8 +33,8 @@ pub fn toml(
|
|||||||
source: Spanned<DataSource>,
|
source: Spanned<DataSource>,
|
||||||
) -> SourceResult<Value> {
|
) -> SourceResult<Value> {
|
||||||
let data = source.load(engine.world)?;
|
let data = source.load(engine.world)?;
|
||||||
let raw = data.as_str()?;
|
let raw = data.load_str()?;
|
||||||
::toml::from_str(raw).map_err(|err| format_toml_error(&data, err))
|
::toml::from_str(raw).map_err(format_toml_error).in_text(&data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
@ -69,10 +69,7 @@ impl toml {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format the user-facing TOML error message.
|
/// Format the user-facing TOML error message.
|
||||||
fn format_toml_error(
|
fn format_toml_error(error: ::toml::de::Error) -> LoadError {
|
||||||
data: &Loaded,
|
let pos = error.span().map(ReportPos::from).unwrap_or_default();
|
||||||
error: ::toml::de::Error,
|
LoadError::new(pos, "failed to parse TOML", error.message())
|
||||||
) -> EcoVec<SourceDiagnostic> {
|
|
||||||
let pos = error.span().map(ReportPos::Range).unwrap_or_default();
|
|
||||||
data.err_in_text(pos, "failed to parse TOML", error.message())
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use ecow::EcoVec;
|
|
||||||
use roxmltree::ParsingOptions;
|
use roxmltree::ParsingOptions;
|
||||||
use typst_syntax::Spanned;
|
use typst_syntax::Spanned;
|
||||||
|
|
||||||
use crate::diag::{format_xml_like_error, SourceDiagnostic, SourceResult};
|
use crate::diag::{format_xml_like_error, LoadError, LoadedAt, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{dict, func, scope, Array, Dict, IntoValue, Str, Value};
|
use crate::foundations::{dict, func, scope, Array, Dict, IntoValue, Str, Value};
|
||||||
use crate::loading::{DataSource, Load, Loaded, Readable};
|
use crate::loading::{DataSource, Load, Readable};
|
||||||
|
|
||||||
/// Reads structured data from an XML file.
|
/// Reads structured data from an XML file.
|
||||||
///
|
///
|
||||||
@ -62,12 +61,13 @@ pub fn xml(
|
|||||||
source: Spanned<DataSource>,
|
source: Spanned<DataSource>,
|
||||||
) -> SourceResult<Value> {
|
) -> SourceResult<Value> {
|
||||||
let data = source.load(engine.world)?;
|
let data = source.load(engine.world)?;
|
||||||
let text = data.as_str()?;
|
let text = data.load_str()?;
|
||||||
let document = roxmltree::Document::parse_with_options(
|
let document = roxmltree::Document::parse_with_options(
|
||||||
text,
|
text,
|
||||||
ParsingOptions { allow_dtd: true, ..Default::default() },
|
ParsingOptions { allow_dtd: true, ..Default::default() },
|
||||||
)
|
)
|
||||||
.map_err(|err| format_xml_error(&data, err))?;
|
.map_err(format_xml_error)
|
||||||
|
.in_text(&data)?;
|
||||||
Ok(convert_xml(document.root()))
|
Ok(convert_xml(document.root()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +110,6 @@ fn convert_xml(node: roxmltree::Node) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format the user-facing XML error message.
|
/// Format the user-facing XML error message.
|
||||||
fn format_xml_error(data: &Loaded, error: roxmltree::Error) -> EcoVec<SourceDiagnostic> {
|
fn format_xml_error(error: roxmltree::Error) -> LoadError {
|
||||||
format_xml_like_error("XML", data, error)
|
format_xml_like_error("XML", error)
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ use typst_syntax::{Span, Spanned};
|
|||||||
use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
|
use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
|
||||||
|
|
||||||
use crate::diag::{
|
use crate::diag::{
|
||||||
bail, error, At, HintedStrResult, ReportPos, SourceDiagnostic, SourceResult,
|
bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedAt, ReportPos,
|
||||||
StrResult,
|
SourceDiagnostic, SourceResult, StrResult,
|
||||||
};
|
};
|
||||||
use crate::engine::{Engine, Sink};
|
use crate::engine::{Engine, Sink};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
@ -34,7 +34,7 @@ use crate::layout::{
|
|||||||
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
|
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
|
||||||
Sides, Sizing, TrackSizings,
|
Sides, Sizing, TrackSizings,
|
||||||
};
|
};
|
||||||
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded};
|
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, LoadStr, Loaded};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
|
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
|
||||||
Url,
|
Url,
|
||||||
@ -356,7 +356,7 @@ impl Debug for Bibliography {
|
|||||||
|
|
||||||
/// Decode on library from one data source.
|
/// Decode on library from one data source.
|
||||||
fn decode_library(data: &Loaded) -> SourceResult<Library> {
|
fn decode_library(data: &Loaded) -> SourceResult<Library> {
|
||||||
let str = data.as_str()?;
|
let str = data.load_str()?;
|
||||||
|
|
||||||
if let LoadSource::Path(file_id) = data.source.v {
|
if let LoadSource::Path(file_id) = data.source.v {
|
||||||
// If we got a path, use the extension to determine whether it is
|
// If we got a path, use the extension to determine whether it is
|
||||||
@ -451,7 +451,7 @@ impl CslStyle {
|
|||||||
CslSource::Named(style) => Self::from_archived(*style),
|
CslSource::Named(style) => Self::from_archived(*style),
|
||||||
CslSource::Normal(source) => {
|
CslSource::Normal(source) => {
|
||||||
let data = Spanned::new(source, span).load(world)?;
|
let data = Spanned::new(source, span).load(world)?;
|
||||||
Self::from_data(&data)?
|
Self::from_data(&data.bytes).in_text(&data)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Derived::new(source, style))
|
Ok(Derived::new(source, style))
|
||||||
@ -472,17 +472,17 @@ impl CslStyle {
|
|||||||
|
|
||||||
/// Load a CSL style from file contents.
|
/// Load a CSL style from file contents.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
pub fn from_data(data: &Loaded) -> SourceResult<CslStyle> {
|
pub fn from_data(bytes: &Bytes) -> LoadResult<CslStyle> {
|
||||||
let text = data.as_str()?;
|
let text = bytes.load_str()?;
|
||||||
citationberg::IndependentStyle::from_xml(text)
|
citationberg::IndependentStyle::from_xml(text)
|
||||||
.map(|style| {
|
.map(|style| {
|
||||||
Self(Arc::new(ManuallyHash::new(
|
Self(Arc::new(ManuallyHash::new(
|
||||||
style,
|
style,
|
||||||
typst_utils::hash128(&(TypeId::of::<Bytes>(), data)),
|
typst_utils::hash128(&(TypeId::of::<Bytes>(), bytes)),
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
data.err_in_text(ReportPos::None, "failed to load CSL style", err)
|
LoadError::new(ReportPos::None, "failed to load CSL style", err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,15 +11,15 @@ use typst_utils::ManuallyHash;
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::Lang;
|
use super::Lang;
|
||||||
use crate::diag::{LineCol, ReportPos, SourceDiagnostic, SourceResult};
|
use crate::diag::{LineCol, LoadError, LoadResult, LoadedAt, ReportPos, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Content, Derived, NativeElement, OneOrMultiple, Packed, PlainText,
|
cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed,
|
||||||
Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
|
PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
|
||||||
};
|
};
|
||||||
use crate::html::{tag, HtmlElem};
|
use crate::html::{tag, HtmlElem};
|
||||||
use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
|
use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
|
||||||
use crate::loading::{DataSource, Load, Loaded};
|
use crate::loading::{DataSource, Load, LoadStr};
|
||||||
use crate::model::{Figurable, ParElem};
|
use crate::model::{Figurable, ParElem};
|
||||||
use crate::text::{FontFamily, FontList, LinebreakElem, LocalName, TextElem, TextSize};
|
use crate::text::{FontFamily, FontList, LinebreakElem, LocalName, TextElem, TextSize};
|
||||||
use crate::visualize::Color;
|
use crate::visualize::Color;
|
||||||
@ -540,25 +540,28 @@ impl RawSyntax {
|
|||||||
sources: Spanned<OneOrMultiple<DataSource>>,
|
sources: Spanned<OneOrMultiple<DataSource>>,
|
||||||
) -> SourceResult<Derived<OneOrMultiple<DataSource>, Vec<RawSyntax>>> {
|
) -> SourceResult<Derived<OneOrMultiple<DataSource>, Vec<RawSyntax>>> {
|
||||||
let data = sources.load(world)?;
|
let data = sources.load(world)?;
|
||||||
let list = data.iter().map(Self::decode).collect::<SourceResult<_>>()?;
|
let list = data
|
||||||
|
.iter()
|
||||||
|
.map(|data| Self::decode(&data.bytes).in_text(data))
|
||||||
|
.collect::<SourceResult<_>>()?;
|
||||||
Ok(Derived::new(sources.v, list))
|
Ok(Derived::new(sources.v, list))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode a syntax from a loaded source.
|
/// Decode a syntax from a loaded source.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
#[typst_macros::time(name = "load syntaxes")]
|
#[typst_macros::time(name = "load syntaxes")]
|
||||||
fn decode(data: &Loaded) -> SourceResult<RawSyntax> {
|
fn decode(bytes: &Bytes) -> LoadResult<RawSyntax> {
|
||||||
let str = data.as_str()?;
|
let str = bytes.load_str()?;
|
||||||
|
|
||||||
let syntax = SyntaxDefinition::load_from_str(str, false, None)
|
let syntax = SyntaxDefinition::load_from_str(str, false, None)
|
||||||
.map_err(|err| format_syntax_error(data, err))?;
|
.map_err(format_syntax_error)?;
|
||||||
|
|
||||||
let mut builder = SyntaxSetBuilder::new();
|
let mut builder = SyntaxSetBuilder::new();
|
||||||
builder.add(syntax);
|
builder.add(syntax);
|
||||||
|
|
||||||
Ok(RawSyntax(Arc::new(ManuallyHash::new(
|
Ok(RawSyntax(Arc::new(ManuallyHash::new(
|
||||||
builder.build(),
|
builder.build(),
|
||||||
typst_utils::hash128(data),
|
typst_utils::hash128(bytes),
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -568,12 +571,9 @@ impl RawSyntax {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_syntax_error(
|
fn format_syntax_error(error: ParseSyntaxError) -> LoadError {
|
||||||
data: &Loaded,
|
|
||||||
error: ParseSyntaxError,
|
|
||||||
) -> EcoVec<SourceDiagnostic> {
|
|
||||||
let pos = syntax_error_pos(&error);
|
let pos = syntax_error_pos(&error);
|
||||||
data.err_in_text(pos, "failed to parse syntax", error)
|
LoadError::new(pos, "failed to parse syntax", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax_error_pos(error: &ParseSyntaxError) -> ReportPos {
|
fn syntax_error_pos(error: &ParseSyntaxError) -> ReportPos {
|
||||||
@ -600,17 +600,17 @@ impl RawTheme {
|
|||||||
source: Spanned<DataSource>,
|
source: Spanned<DataSource>,
|
||||||
) -> SourceResult<Derived<DataSource, Self>> {
|
) -> SourceResult<Derived<DataSource, Self>> {
|
||||||
let data = source.load(world)?;
|
let data = source.load(world)?;
|
||||||
let theme = Self::decode(&data)?;
|
let theme = Self::decode(&data.bytes).in_text(&data)?;
|
||||||
Ok(Derived::new(source.v, theme))
|
Ok(Derived::new(source.v, theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode a theme from bytes.
|
/// Decode a theme from bytes.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn decode(data: &Loaded) -> SourceResult<RawTheme> {
|
fn decode(bytes: &Bytes) -> LoadResult<RawTheme> {
|
||||||
let mut cursor = std::io::Cursor::new(data.bytes.as_slice());
|
let mut cursor = std::io::Cursor::new(bytes.as_slice());
|
||||||
let theme = synt::ThemeSet::load_from_reader(&mut cursor)
|
let theme =
|
||||||
.map_err(|err| format_theme_error(data, err))?;
|
synt::ThemeSet::load_from_reader(&mut cursor).map_err(format_theme_error)?;
|
||||||
Ok(RawTheme(Arc::new(ManuallyHash::new(theme, typst_utils::hash128(data)))))
|
Ok(RawTheme(Arc::new(ManuallyHash::new(theme, typst_utils::hash128(bytes)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the underlying syntect theme.
|
/// Get the underlying syntect theme.
|
||||||
@ -619,15 +619,12 @@ impl RawTheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_theme_error(
|
fn format_theme_error(error: syntect::LoadingError) -> LoadError {
|
||||||
data: &Loaded,
|
|
||||||
error: syntect::LoadingError,
|
|
||||||
) -> EcoVec<SourceDiagnostic> {
|
|
||||||
let pos = match &error {
|
let pos = match &error {
|
||||||
syntect::LoadingError::ParseSyntax(err, _) => syntax_error_pos(err),
|
syntect::LoadingError::ParseSyntax(err, _) => syntax_error_pos(err),
|
||||||
_ => ReportPos::None,
|
_ => ReportPos::None,
|
||||||
};
|
};
|
||||||
data.err_in_text(pos, "failed to parse theme", error)
|
LoadError::new(pos, "failed to parse theme", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A highlighted line of raw text.
|
/// A highlighted line of raw text.
|
||||||
|
@ -22,7 +22,7 @@ use crate::foundations::{
|
|||||||
Smart, StyleChain,
|
Smart, StyleChain,
|
||||||
};
|
};
|
||||||
use crate::layout::{BlockElem, Length, Rel, Sizing};
|
use crate::layout::{BlockElem, Length, Rel, Sizing};
|
||||||
use crate::loading::{DataSource, Load, Readable};
|
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
||||||
use crate::model::Figurable;
|
use crate::model::Figurable;
|
||||||
use crate::text::LocalName;
|
use crate::text::LocalName;
|
||||||
|
|
||||||
@ -66,9 +66,9 @@ pub struct ImageElem {
|
|||||||
#[parse(
|
#[parse(
|
||||||
let source = args.expect::<Spanned<DataSource>>("source")?;
|
let source = args.expect::<Spanned<DataSource>>("source")?;
|
||||||
let data = source.load(engine.world)?;
|
let data = source.load(engine.world)?;
|
||||||
Derived::new(source.v, data.bytes)
|
Derived::new(source.v, data)
|
||||||
)]
|
)]
|
||||||
pub source: Derived<DataSource, Bytes>,
|
pub source: Derived<DataSource, Loaded>,
|
||||||
|
|
||||||
/// The image's format.
|
/// The image's format.
|
||||||
///
|
///
|
||||||
@ -173,7 +173,7 @@ impl ImageElem {
|
|||||||
pub fn decode(
|
pub fn decode(
|
||||||
span: Span,
|
span: Span,
|
||||||
/// The data to decode as an image. Can be a string for SVGs.
|
/// The data to decode as an image. Can be a string for SVGs.
|
||||||
data: Readable,
|
data: Spanned<Readable>,
|
||||||
/// The image's format. Detected automatically by default.
|
/// The image's format. Detected automatically by default.
|
||||||
#[named]
|
#[named]
|
||||||
format: Option<Smart<ImageFormat>>,
|
format: Option<Smart<ImageFormat>>,
|
||||||
@ -193,8 +193,9 @@ impl ImageElem {
|
|||||||
#[named]
|
#[named]
|
||||||
scaling: Option<Smart<ImageScaling>>,
|
scaling: Option<Smart<ImageScaling>>,
|
||||||
) -> StrResult<Content> {
|
) -> StrResult<Content> {
|
||||||
let bytes = data.into_bytes();
|
let bytes = data.v.into_bytes();
|
||||||
let source = Derived::new(DataSource::Bytes(bytes.clone()), bytes);
|
let data = Loaded::new(Spanned::new(LoadSource::Bytes, data.span), bytes.clone());
|
||||||
|
let source = Derived::new(DataSource::Bytes(bytes), data);
|
||||||
let mut elem = ImageElem::new(source);
|
let mut elem = ImageElem::new(source);
|
||||||
if let Some(format) = format {
|
if let Some(format) = format {
|
||||||
elem.push_format(format);
|
elem.push_format(format);
|
||||||
|
@ -3,13 +3,11 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::EcoString;
|
|
||||||
use siphasher::sip128::{Hasher128, SipHasher13};
|
use siphasher::sip128::{Hasher128, SipHasher13};
|
||||||
|
|
||||||
use crate::diag::{format_xml_like_error, StrResult};
|
use crate::diag::{format_xml_like_error, LoadError, LoadResult, ReportPos};
|
||||||
use crate::foundations::Bytes;
|
use crate::foundations::Bytes;
|
||||||
use crate::layout::Axes;
|
use crate::layout::Axes;
|
||||||
use crate::loading::Loaded;
|
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
Font, FontBook, FontFlags, FontStretch, FontStyle, FontVariant, FontWeight,
|
Font, FontBook, FontFlags, FontStretch, FontStyle, FontVariant, FontWeight,
|
||||||
};
|
};
|
||||||
@ -31,7 +29,7 @@ impl SvgImage {
|
|||||||
/// Decode an SVG image without fonts.
|
/// Decode an SVG image without fonts.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
#[typst_macros::time(name = "load svg")]
|
#[typst_macros::time(name = "load svg")]
|
||||||
pub fn new(data: Bytes) -> StrResult<SvgImage> {
|
pub fn new(data: Bytes) -> LoadResult<SvgImage> {
|
||||||
let tree =
|
let tree =
|
||||||
usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?;
|
usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?;
|
||||||
Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash: 0, tree })))
|
Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash: 0, tree })))
|
||||||
@ -44,7 +42,7 @@ impl SvgImage {
|
|||||||
data: Bytes,
|
data: Bytes,
|
||||||
world: Tracked<dyn World + '_>,
|
world: Tracked<dyn World + '_>,
|
||||||
families: &[&str],
|
families: &[&str],
|
||||||
) -> StrResult<SvgImage> {
|
) -> LoadResult<SvgImage> {
|
||||||
let book = world.book();
|
let book = world.book();
|
||||||
let resolver = Mutex::new(FontResolver::new(world, book, families));
|
let resolver = Mutex::new(FontResolver::new(world, book, families));
|
||||||
let tree = usvg::Tree::from_data(
|
let tree = usvg::Tree::from_data(
|
||||||
@ -126,21 +124,15 @@ fn tree_size(tree: &usvg::Tree) -> Axes<f64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format the user-facing SVG decoding error message.
|
/// Format the user-facing SVG decoding error message.
|
||||||
fn format_usvg_error(error: usvg::Error) -> EcoString {
|
fn format_usvg_error(error: usvg::Error) -> LoadError {
|
||||||
match error {
|
let error = match error {
|
||||||
usvg::Error::NotAnUtf8Str => "file is not valid utf-8".into(),
|
usvg::Error::NotAnUtf8Str => "file is not valid utf-8",
|
||||||
usvg::Error::MalformedGZip => "file is not compressed correctly".into(),
|
usvg::Error::MalformedGZip => "file is not compressed correctly",
|
||||||
usvg::Error::ElementsLimitReached => "file is too large".into(),
|
usvg::Error::ElementsLimitReached => "file is too large",
|
||||||
usvg::Error::InvalidSize => {
|
usvg::Error::InvalidSize => "width, height, or viewbox is invalid",
|
||||||
"failed to parse SVG (width, height, or viewbox is invalid)".into()
|
usvg::Error::ParsingFailed(error) => return format_xml_like_error("SVG", error),
|
||||||
}
|
};
|
||||||
usvg::Error::ParsingFailed(error) => {
|
LoadError::new(ReportPos::None, "failed to parse SVG", error)
|
||||||
format_xml_like_error("SVG", &Loaded::dummy(), error)
|
|
||||||
.pop()
|
|
||||||
.expect("at least one error")
|
|
||||||
.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides Typst's fonts to usvg.
|
/// Provides Typst's fonts to usvg.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user