Remove image store

This commit is contained in:
Laurenz 2022-09-19 12:49:36 +02:00
parent 30be75c668
commit 59f67b79c7
16 changed files with 293 additions and 335 deletions

View File

@ -9,7 +9,7 @@ use crate::util::SliceExt;
/// Embed all used fonts into the PDF. /// Embed all used fonts into the PDF.
pub fn write_fonts(ctx: &mut PdfContext) { pub fn write_fonts(ctx: &mut PdfContext) {
for font_id in ctx.font_map.layout_indices() { for &font_id in ctx.font_map.items() {
let type0_ref = ctx.alloc.bump(); let type0_ref = ctx.alloc.bump();
let cid_ref = ctx.alloc.bump(); let cid_ref = ctx.alloc.bump();
let descriptor_ref = ctx.alloc.bump(); let descriptor_ref = ctx.alloc.bump();

View File

@ -1,25 +1,26 @@
use std::io::Cursor; use std::io::Cursor;
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba}; use image::{DynamicImage, GenericImageView, ImageResult, Rgba};
use pdf_writer::{Filter, Finish}; use pdf_writer::{Filter, Finish};
use super::{deflate, PdfContext, RefExt}; use super::{deflate, PdfContext, RefExt};
use crate::image::{Image, RasterImage}; use crate::image::{DecodedImage, ImageFormat};
/// Embed all used images into the PDF. /// Embed all used images into the PDF.
pub fn write_images(ctx: &mut PdfContext) { pub fn write_images(ctx: &mut PdfContext) {
for image_id in ctx.image_map.layout_indices() { for image in ctx.image_map.items() {
let image_ref = ctx.alloc.bump(); let image_ref = ctx.alloc.bump();
ctx.image_refs.push(image_ref); ctx.image_refs.push(image_ref);
let img = ctx.images.get(image_id); let width = image.width();
let width = img.width(); let height = image.height();
let height = img.height();
// Add the primary image. // Add the primary image.
match img { match image.decode().unwrap() {
Image::Raster(img) => { DecodedImage::Raster(dynamic) => {
if let Ok((data, filter, has_color)) = encode_image(img) { if let Ok((data, filter, has_color)) =
encode_image(image.format(), &dynamic)
{
let mut image = ctx.writer.image_xobject(image_ref, &data); let mut image = ctx.writer.image_xobject(image_ref, &data);
image.filter(filter); image.filter(filter);
image.width(width as i32); image.width(width as i32);
@ -35,8 +36,8 @@ pub fn write_images(ctx: &mut PdfContext) {
// Add a second gray-scale image containing the alpha values if // Add a second gray-scale image containing the alpha values if
// this image has an alpha channel. // this image has an alpha channel.
if img.buf.color().has_alpha() { if dynamic.color().has_alpha() {
let (alpha_data, alpha_filter) = encode_alpha(img); let (alpha_data, alpha_filter) = encode_alpha(&dynamic);
let mask_ref = ctx.alloc.bump(); let mask_ref = ctx.alloc.bump();
image.s_mask(mask_ref); image.s_mask(mask_ref);
image.finish(); image.finish();
@ -59,9 +60,9 @@ pub fn write_images(ctx: &mut PdfContext) {
.device_gray(); .device_gray();
} }
} }
Image::Svg(img) => { DecodedImage::Svg(svg) => {
let next_ref = svg2pdf::convert_tree_into( let next_ref = svg2pdf::convert_tree_into(
&img.0, &svg,
svg2pdf::Options::default(), svg2pdf::Options::default(),
&mut ctx.writer, &mut ctx.writer,
image_ref, image_ref,
@ -76,19 +77,22 @@ pub fn write_images(ctx: &mut PdfContext) {
/// whether the image has color. /// whether the image has color.
/// ///
/// Skips the alpha channel as that's encoded separately. /// Skips the alpha channel as that's encoded separately.
fn encode_image(img: &RasterImage) -> ImageResult<(Vec<u8>, Filter, bool)> { fn encode_image(
Ok(match (img.format, &img.buf) { format: ImageFormat,
dynamic: &DynamicImage,
) -> ImageResult<(Vec<u8>, Filter, bool)> {
Ok(match (format, dynamic) {
// 8-bit gray JPEG. // 8-bit gray JPEG.
(ImageFormat::Jpeg, DynamicImage::ImageLuma8(_)) => { (ImageFormat::Jpg, DynamicImage::ImageLuma8(_)) => {
let mut data = Cursor::new(vec![]); let mut data = Cursor::new(vec![]);
img.buf.write_to(&mut data, img.format)?; dynamic.write_to(&mut data, image::ImageFormat::Jpeg)?;
(data.into_inner(), Filter::DctDecode, false) (data.into_inner(), Filter::DctDecode, false)
} }
// 8-bit RGB JPEG (CMYK JPEGs get converted to RGB earlier). // 8-bit RGB JPEG (CMYK JPEGs get converted to RGB earlier).
(ImageFormat::Jpeg, DynamicImage::ImageRgb8(_)) => { (ImageFormat::Jpg, DynamicImage::ImageRgb8(_)) => {
let mut data = Cursor::new(vec![]); let mut data = Cursor::new(vec![]);
img.buf.write_to(&mut data, img.format)?; dynamic.write_to(&mut data, image::ImageFormat::Jpeg)?;
(data.into_inner(), Filter::DctDecode, true) (data.into_inner(), Filter::DctDecode, true)
} }
@ -117,7 +121,7 @@ fn encode_image(img: &RasterImage) -> ImageResult<(Vec<u8>, Filter, bool)> {
} }
/// Encode an image's alpha channel if present. /// Encode an image's alpha channel if present.
fn encode_alpha(img: &RasterImage) -> (Vec<u8>, Filter) { fn encode_alpha(dynamic: &DynamicImage) -> (Vec<u8>, Filter) {
let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect(); let pixels: Vec<_> = dynamic.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
(deflate(&pixels), Filter::FlateDecode) (deflate(&pixels), Filter::FlateDecode)
} }

View File

@ -17,7 +17,7 @@ use self::page::Page;
use crate::font::{FontId, FontStore}; use crate::font::{FontId, FontStore};
use crate::frame::Frame; use crate::frame::Frame;
use crate::geom::{Dir, Em, Length}; use crate::geom::{Dir, Em, Length};
use crate::image::{ImageId, ImageStore}; use crate::image::Image;
use crate::library::text::Lang; use crate::library::text::Lang;
use crate::Context; use crate::Context;
@ -46,7 +46,6 @@ const D65_GRAY: Name<'static> = Name(b"d65gray");
pub struct PdfContext<'a> { pub struct PdfContext<'a> {
writer: PdfWriter, writer: PdfWriter,
fonts: &'a FontStore, fonts: &'a FontStore,
images: &'a ImageStore,
pages: Vec<Page>, pages: Vec<Page>,
page_heights: Vec<f32>, page_heights: Vec<f32>,
alloc: Ref, alloc: Ref,
@ -55,7 +54,7 @@ pub struct PdfContext<'a> {
image_refs: Vec<Ref>, image_refs: Vec<Ref>,
page_refs: Vec<Ref>, page_refs: Vec<Ref>,
font_map: Remapper<FontId>, font_map: Remapper<FontId>,
image_map: Remapper<ImageId>, image_map: Remapper<Image>,
glyph_sets: HashMap<FontId, HashSet<u16>>, glyph_sets: HashMap<FontId, HashSet<u16>>,
languages: HashMap<Lang, usize>, languages: HashMap<Lang, usize>,
heading_tree: Vec<HeadingNode>, heading_tree: Vec<HeadingNode>,
@ -68,7 +67,6 @@ impl<'a> PdfContext<'a> {
Self { Self {
writer: PdfWriter::new(), writer: PdfWriter::new(),
fonts: &ctx.fonts, fonts: &ctx.fonts,
images: &ctx.images,
pages: vec![], pages: vec![],
page_heights: vec![], page_heights: vec![],
alloc, alloc,
@ -147,36 +145,33 @@ fn deflate(data: &[u8]) -> Vec<u8> {
miniz_oxide::deflate::compress_to_vec_zlib(data, COMPRESSION_LEVEL) miniz_oxide::deflate::compress_to_vec_zlib(data, COMPRESSION_LEVEL)
} }
/// Assigns new, consecutive PDF-internal indices to things. /// Assigns new, consecutive PDF-internal indices to items.
struct Remapper<Index> { struct Remapper<T> {
/// Forwards from the old indices to the new pdf indices. /// Forwards from the items to the pdf indices.
to_pdf: HashMap<Index, usize>, to_pdf: HashMap<T, usize>,
/// Backwards from the pdf indices to the old indices. /// Backwards from the pdf indices to the items.
to_layout: Vec<Index>, to_items: Vec<T>,
} }
impl<Index> Remapper<Index> impl<T> Remapper<T>
where where
Index: Copy + Eq + Hash, T: Eq + Hash + Clone,
{ {
fn new() -> Self { fn new() -> Self {
Self { Self { to_pdf: HashMap::new(), to_items: vec![] }
to_pdf: HashMap::new(),
to_layout: vec![],
}
} }
fn insert(&mut self, index: Index) { fn insert(&mut self, item: T) {
let to_layout = &mut self.to_layout; let to_layout = &mut self.to_items;
self.to_pdf.entry(index).or_insert_with(|| { self.to_pdf.entry(item.clone()).or_insert_with(|| {
let pdf_index = to_layout.len(); let pdf_index = to_layout.len();
to_layout.push(index); to_layout.push(item);
pdf_index pdf_index
}); });
} }
fn map(&self, index: Index) -> usize { fn map(&self, item: T) -> usize {
self.to_pdf[&index] self.to_pdf[&item]
} }
fn pdf_indices<'a>( fn pdf_indices<'a>(
@ -186,8 +181,8 @@ where
refs.iter().copied().zip(0 .. self.to_pdf.len()) refs.iter().copied().zip(0 .. self.to_pdf.len())
} }
fn layout_indices(&self) -> impl Iterator<Item = Index> + '_ { fn items(&self) -> impl Iterator<Item = &T> + '_ {
self.to_layout.iter().copied() self.to_items.iter()
} }
} }

View File

@ -11,7 +11,7 @@ use crate::geom::{
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
Transform, Transform,
}; };
use crate::image::ImageId; use crate::image::Image;
/// Construct page objects. /// Construct page objects.
pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) { pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) {
@ -290,13 +290,12 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
for &(pos, ref element) in frame.elements() { for &(pos, ref element) in frame.elements() {
let x = pos.x.to_f32(); let x = pos.x.to_f32();
let y = pos.y.to_f32(); let y = pos.y.to_f32();
match *element { match element {
Element::Group(ref group) => write_group(ctx, pos, group), Element::Group(group) => write_group(ctx, pos, group),
Element::Text(ref text) => write_text(ctx, x, y, text), Element::Text(text) => write_text(ctx, x, y, text),
Element::Shape(ref shape) => write_shape(ctx, x, y, shape), Element::Shape(shape) => write_shape(ctx, x, y, shape),
Element::Image(id, size) => write_image(ctx, x, y, id, size), Element::Image(image, size) => write_image(ctx, x, y, image, *size),
Element::Link(ref dest, size) => write_link(ctx, pos, dest, size), Element::Link(dest, size) => write_link(ctx, pos, dest, *size),
Element::Pin(_) => {}
} }
} }
} }
@ -449,9 +448,9 @@ fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) {
} }
/// Encode a vector or raster image into the content stream. /// Encode a vector or raster image into the content stream.
fn write_image(ctx: &mut PageContext, x: f32, y: f32, id: ImageId, size: Size) { fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) {
ctx.parent.image_map.insert(id); ctx.parent.image_map.insert(image.clone());
let name = format_eco!("Im{}", ctx.parent.image_map.map(id)); let name = format_eco!("Im{}", ctx.parent.image_map.map(image.clone()));
let w = size.x.to_f32(); let w = size.x.to_f32();
let h = size.y.to_f32(); let h = size.y.to_f32();
ctx.content.save_state(); ctx.content.save_state();

View File

@ -12,7 +12,7 @@ use crate::frame::{Element, Frame, Group, Text};
use crate::geom::{ use crate::geom::{
self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform, self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform,
}; };
use crate::image::{Image, RasterImage, Svg}; use crate::image::{DecodedImage, Image};
use crate::Context; use crate::Context;
/// Export a frame into a rendered image. /// Export a frame into a rendered image.
@ -49,21 +49,20 @@ fn render_frame(
let y = pos.y.to_f32(); let y = pos.y.to_f32();
let ts = ts.pre_translate(x, y); let ts = ts.pre_translate(x, y);
match *element { match element {
Element::Group(ref group) => { Element::Group(group) => {
render_group(canvas, ts, mask, ctx, group); render_group(canvas, ts, mask, ctx, group);
} }
Element::Text(ref text) => { Element::Text(text) => {
render_text(canvas, ts, mask, ctx, text); render_text(canvas, ts, mask, ctx, text);
} }
Element::Shape(ref shape) => { Element::Shape(shape) => {
render_shape(canvas, ts, mask, shape); render_shape(canvas, ts, mask, shape);
} }
Element::Image(id, size) => { Element::Image(image, size) => {
render_image(canvas, ts, mask, ctx.images.get(id), size); render_image(canvas, ts, mask, image, *size);
} }
Element::Link(_, _) => {} Element::Link(_, _) => {}
Element::Pin(_) => {}
} }
} }
} }
@ -197,17 +196,20 @@ fn render_bitmap_glyph(
let ppem = size * ts.sy; let ppem = size * ts.sy;
let font = ctx.fonts.get(text.font_id); let font = ctx.fonts.get(text.font_id);
let raster = font.ttf().glyph_raster_image(id, ppem as u16)?; let raster = font.ttf().glyph_raster_image(id, ppem as u16)?;
let img = RasterImage::parse(&raster.data).ok()?; let ext = match raster.format {
ttf_parser::RasterImageFormat::PNG => "png",
};
let image = Image::new(raster.data.into(), ext).ok()?;
// FIXME: Vertical alignment isn't quite right for Apple Color Emoji, // FIXME: Vertical alignment isn't quite right for Apple Color Emoji,
// and maybe also for Noto Color Emoji. And: Is the size calculation // and maybe also for Noto Color Emoji. And: Is the size calculation
// correct? // correct?
let h = text.size; let h = text.size;
let w = (img.width() as f64 / img.height() as f64) * h; let w = (image.width() as f64 / image.height() as f64) * h;
let dx = (raster.x as f32) / (img.width() as f32) * size; let dx = (raster.x as f32) / (image.width() as f32) * size;
let dy = (raster.y as f32) / (img.height() as f32) * size; let dy = (raster.y as f32) / (image.height() as f32) * size;
let ts = ts.pre_translate(dx, -size - dy); let ts = ts.pre_translate(dx, -size - dy);
render_image(canvas, ts, mask, &Image::Raster(img), Size::new(w, h)) render_image(canvas, ts, mask, &image, Size::new(w, h))
} }
/// Render an outline glyph into the canvas. This is the "normal" case. /// Render an outline glyph into the canvas. This is the "normal" case.
@ -338,33 +340,33 @@ fn render_image(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
mask: Option<&sk::ClipMask>, mask: Option<&sk::ClipMask>,
img: &Image, image: &Image,
size: Size, size: Size,
) -> Option<()> { ) -> Option<()> {
let view_width = size.x.to_f32(); let view_width = size.x.to_f32();
let view_height = size.y.to_f32(); let view_height = size.y.to_f32();
let aspect = (img.width() as f32) / (img.height() as f32); let aspect = (image.width() as f32) / (image.height() as f32);
let scale = ts.sx.max(ts.sy); let scale = ts.sx.max(ts.sy);
let w = (scale * view_width.max(aspect * view_height)).ceil() as u32; let w = (scale * view_width.max(aspect * view_height)).ceil() as u32;
let h = ((w as f32) / aspect).ceil() as u32; let h = ((w as f32) / aspect).ceil() as u32;
let mut pixmap = sk::Pixmap::new(w, h)?; let mut pixmap = sk::Pixmap::new(w, h)?;
match img { match image.decode().unwrap() {
Image::Raster(img) => { DecodedImage::Raster(dynamic) => {
let downscale = w < img.width(); let downscale = w < image.width();
let filter = if downscale { let filter = if downscale {
FilterType::Lanczos3 FilterType::Lanczos3
} else { } else {
FilterType::CatmullRom FilterType::CatmullRom
}; };
let buf = img.buf.resize(w, h, filter); let buf = dynamic.resize(w, h, filter);
for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) { for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) {
let Rgba([r, g, b, a]) = src; let Rgba([r, g, b, a]) = src;
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
} }
} }
Image::Svg(Svg(tree)) => { DecodedImage::Svg(tree) => {
resvg::render( resvg::render(
&tree, &tree,
FitTo::Size(w, h), FitTo::Size(w, h),

View File

@ -13,7 +13,7 @@ use ttf_parser::{name_id, GlyphId, PlatformId, Tag};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::geom::Em; use crate::geom::Em;
use crate::loading::{FileHash, Loader}; use crate::loading::{Buffer, FileHash, Loader};
/// A unique identifier for a loaded font. /// A unique identifier for a loaded font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@ -40,7 +40,7 @@ pub struct FontStore {
failed: Vec<bool>, failed: Vec<bool>,
fonts: Vec<Option<Font>>, fonts: Vec<Option<Font>>,
families: BTreeMap<String, Vec<FontId>>, families: BTreeMap<String, Vec<FontId>>,
buffers: HashMap<FileHash, Arc<Vec<u8>>>, buffers: HashMap<FileHash, Buffer>,
} }
impl FontStore { impl FontStore {
@ -214,11 +214,11 @@ impl FontStore {
Entry::Occupied(entry) => entry.into_mut(), Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
let buffer = self.loader.load(path).ok()?; let buffer = self.loader.load(path).ok()?;
entry.insert(Arc::new(buffer)) entry.insert(buffer)
} }
}; };
let font = Font::new(Arc::clone(buffer), index)?; let font = Font::new(buffer.clone(), index)?;
*slot = Some(font); *slot = Some(font);
self.failed[idx] = false; self.failed[idx] = false;
@ -239,7 +239,7 @@ pub struct Font {
/// The raw font data, possibly shared with other fonts from the same /// The raw font data, possibly shared with other fonts from the same
/// collection. The vector's allocation must not move, because `ttf` points /// collection. The vector's allocation must not move, because `ttf` points
/// into it using unsafe code. /// into it using unsafe code.
buffer: Arc<Vec<u8>>, data: Buffer,
/// The font's index in the collection (zero if not a collection). /// The font's index in the collection (zero if not a collection).
index: u32, index: u32,
/// The underlying ttf-parser/rustybuzz face. /// The underlying ttf-parser/rustybuzz face.
@ -251,8 +251,8 @@ pub struct Font {
} }
impl Font { impl Font {
/// Parse a font from a buffer and collection index. /// Parse a font from data and collection index.
pub fn new(buffer: Arc<Vec<u8>>, index: u32) -> Option<Self> { pub fn new(data: Buffer, index: u32) -> Option<Self> {
// Safety: // Safety:
// - The slices's location is stable in memory: // - The slices's location is stable in memory:
// - We don't move the underlying vector // - We don't move the underlying vector
@ -260,13 +260,13 @@ impl Font {
// - The internal 'static lifetime is not leaked because its rewritten // - The internal 'static lifetime is not leaked because its rewritten
// to the self-lifetime in `ttf()`. // to the self-lifetime in `ttf()`.
let slice: &'static [u8] = let slice: &'static [u8] =
unsafe { std::slice::from_raw_parts(buffer.as_ptr(), buffer.len()) }; unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
let ttf = rustybuzz::Face::from_slice(slice, index)?; let ttf = rustybuzz::Face::from_slice(slice, index)?;
let metrics = FontMetrics::from_ttf(&ttf); let metrics = FontMetrics::from_ttf(&ttf);
Some(Self { Some(Self {
buffer, data,
index, index,
ttf, ttf,
metrics, metrics,
@ -275,8 +275,8 @@ impl Font {
} }
/// The underlying buffer. /// The underlying buffer.
pub fn buffer(&self) -> &Arc<Vec<u8>> { pub fn buffer(&self) -> &Buffer {
&self.buffer &self.data
} }
/// The collection index. /// The collection index.

View File

@ -9,7 +9,7 @@ use crate::font::FontId;
use crate::geom::{ use crate::geom::{
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform, Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
}; };
use crate::image::ImageId; use crate::image::Image;
use crate::library::text::Lang; use crate::library::text::Lang;
use crate::util::EcoString; use crate::util::EcoString;
@ -303,12 +303,9 @@ pub enum Element {
/// A geometric shape with optional fill and stroke. /// A geometric shape with optional fill and stroke.
Shape(Shape), Shape(Shape),
/// An image and its size. /// An image and its size.
Image(ImageId, Size), Image(Image, Size),
/// A link to an external resource and its trigger region. /// A link to an external resource and its trigger region.
Link(Destination, Size), Link(Destination, Size),
/// A pin identified by index. This is used to find elements on the pages
/// and use their location in formatting. Exporters can just ignore it.
Pin(usize),
} }
impl Debug for Element { impl Debug for Element {
@ -319,7 +316,6 @@ impl Debug for Element {
Self::Shape(shape) => write!(f, "{shape:?}"), Self::Shape(shape) => write!(f, "{shape:?}"),
Self::Image(image, _) => write!(f, "{image:?}"), Self::Image(image, _) => write!(f, "{image:?}"),
Self::Link(dest, _) => write!(f, "Link({dest:?})"), Self::Link(dest, _) => write!(f, "Link({dest:?})"),
Self::Pin(idx) => write!(f, "Pin({idx})"),
} }
} }
} }

View File

@ -1,213 +1,139 @@
//! Image handling. //! Image handling.
use std::collections::{hash_map::Entry, HashMap};
use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter};
use std::io; use std::io;
use std::path::Path;
use std::sync::Arc;
use image::io::Reader as ImageReader; use crate::loading::Buffer;
use image::{DynamicImage, ImageFormat};
use crate::diag::{failed_to_load, StrResult}; /// A raster or vector image.
use crate::loading::{FileHash, Loader}; ///
/// Values of this type are cheap to clone and hash.
/// A unique identifier for a loaded image. #[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Image {
pub struct ImageId(u32); /// The raw, undecoded image data.
data: Buffer,
impl ImageId { /// The format of the encoded `buffer`.
/// Create an image id from the raw underlying value. format: ImageFormat,
/// /// The width in pixels.
/// This should only be called with values returned by width: u32,
/// [`into_raw`](Self::into_raw). /// The height in pixels.
pub const fn from_raw(v: u32) -> Self { height: u32,
Self(v)
}
/// Convert into the raw underlying value.
pub const fn into_raw(self) -> u32 {
self.0
}
} }
/// Storage for loaded and decoded images. /// A decoded image.
pub struct ImageStore { pub enum DecodedImage {
loader: Arc<dyn Loader>,
files: HashMap<FileHash, ImageId>,
images: Vec<Image>,
}
impl ImageStore {
/// Create a new, empty image store.
pub fn new(loader: Arc<dyn Loader>) -> Self {
Self {
loader,
files: HashMap::new(),
images: vec![],
}
}
/// Get a reference to a loaded image.
///
/// This panics if no image with this `id` was loaded. This function should
/// only be called with ids returned by this store's [`load()`](Self::load)
/// method.
#[track_caller]
pub fn get(&self, id: ImageId) -> &Image {
&self.images[id.0 as usize]
}
/// Load and decode an image file from a path relative to the compilation
/// environment's root.
pub fn load(&mut self, path: &Path) -> StrResult<ImageId> {
let mut try_load = || -> io::Result<ImageId> {
let hash = self.loader.resolve(path)?;
Ok(*match self.files.entry(hash) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let buffer = self.loader.load(path)?;
let ext =
path.extension().and_then(OsStr::to_str).unwrap_or_default();
let image = Image::parse(&buffer, ext)?;
let id = ImageId(self.images.len() as u32);
self.images.push(image);
entry.insert(id)
}
})
};
try_load().map_err(|err| failed_to_load("image", path, err))
}
}
/// A loaded image.
#[derive(Debug)]
pub enum Image {
/// A pixel raster format, like PNG or JPEG. /// A pixel raster format, like PNG or JPEG.
Raster(RasterImage), Raster(image::DynamicImage),
/// An SVG vector graphic. /// An SVG vector graphic.
Svg(Svg), Svg(usvg::Tree),
}
/// A raster or vector image format.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ImageFormat {
/// Raster format for illustrations and transparent graphics.
Png,
/// Lossy raster format suitable for photos.
Jpg,
/// Raster format that is typically used for short animated clips.
Gif,
/// The vector graphics format of the web.
Svg,
} }
impl Image { impl Image {
/// Parse an image from raw data. The file extension is used as a hint for /// Create an image from a raw buffer and a file extension.
/// which error message describes the problem best. ///
pub fn parse(data: &[u8], ext: &str) -> io::Result<Self> { /// The file extension is used to determine the format.
match Svg::parse(data) { pub fn new(data: Buffer, ext: &str) -> io::Result<Self> {
Ok(svg) => return Ok(Self::Svg(svg)), let format = match ext {
Err(err) if matches!(ext, "svg" | "svgz") => return Err(err), "svg" | "svgz" => ImageFormat::Svg,
Err(_) => {} "png" => ImageFormat::Png,
} "jpg" | "jpeg" => ImageFormat::Jpg,
"gif" => ImageFormat::Gif,
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"unknown image format",
));
}
};
match RasterImage::parse(data) { let (width, height) = match format {
Ok(raster) => return Ok(Self::Raster(raster)), ImageFormat::Svg => {
Err(err) if matches!(ext, "png" | "jpg" | "jpeg" | "gif") => return Err(err), let opts = usvg::Options::default();
Err(_) => {} let tree =
} usvg::Tree::from_data(&data, &opts.to_ref()).map_err(invalid)?;
Err(io::Error::new( let size = tree.svg_node().size;
io::ErrorKind::InvalidData, let width = size.width().ceil() as u32;
"unknown image format", let height = size.height().ceil() as u32;
)) (width, height)
}
_ => {
let cursor = io::Cursor::new(&data);
let format = convert_format(format);
let reader = image::io::Reader::with_format(cursor, format);
reader.into_dimensions().map_err(invalid)?
}
};
Ok(Self { data, format, width, height })
}
/// The raw image data.
pub fn data(&self) -> &Buffer {
&self.data
}
/// The format of the image.
pub fn format(&self) -> ImageFormat {
self.format
} }
/// The width of the image in pixels. /// The width of the image in pixels.
pub fn width(&self) -> u32 { pub fn width(&self) -> u32 {
match self { self.width
Self::Raster(image) => image.width(),
Self::Svg(image) => image.width(),
}
} }
/// The height of the image in pixels. /// The height of the image in pixels.
pub fn height(&self) -> u32 { pub fn height(&self) -> u32 {
match self { self.height
Self::Raster(image) => image.height(), }
Self::Svg(image) => image.height(),
} /// Decode the image.
pub fn decode(&self) -> io::Result<DecodedImage> {
Ok(match self.format {
ImageFormat::Svg => {
let opts = usvg::Options::default();
let tree =
usvg::Tree::from_data(&self.data, &opts.to_ref()).map_err(invalid)?;
DecodedImage::Svg(tree)
}
_ => {
let cursor = io::Cursor::new(&self.data);
let format = convert_format(self.format);
let reader = image::io::Reader::with_format(cursor, format);
let dynamic = reader.decode().map_err(invalid)?;
DecodedImage::Raster(dynamic)
}
})
} }
} }
/// A raster image, supported through the image crate. /// Convert a raster image format to the image crate's format.
pub struct RasterImage { fn convert_format(format: ImageFormat) -> image::ImageFormat {
/// The original format the image was encoded in. match format {
pub format: ImageFormat, ImageFormat::Png => image::ImageFormat::Png,
/// The decoded image. ImageFormat::Jpg => image::ImageFormat::Jpeg,
pub buf: DynamicImage, ImageFormat::Gif => image::ImageFormat::Gif,
} ImageFormat::Svg => panic!("must be a raster format"),
impl RasterImage {
/// Parse an image from raw data in a supported format (PNG, JPEG or GIF).
///
/// The image format is determined automatically.
pub fn parse(data: &[u8]) -> io::Result<Self> {
let cursor = io::Cursor::new(data);
let reader = ImageReader::new(cursor).with_guessed_format()?;
let format = reader
.format()
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
let buf = reader
.decode()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
Ok(Self { format, buf })
}
/// The width of the image.
pub fn width(&self) -> u32 {
self.buf.width()
}
/// The height of the image.
pub fn height(&self) -> u32 {
self.buf.height()
} }
} }
impl Debug for RasterImage { /// Turn any error into an I/O error.
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn invalid<E>(error: E) -> io::Error
f.debug_struct("Image") where
.field("format", &self.format) E: std::error::Error + Send + Sync + 'static,
.field("color", &self.buf.color()) {
.field("width", &self.width()) io::Error::new(io::ErrorKind::InvalidData, error)
.field("height", &self.height())
.finish()
}
}
/// An SVG image, supported through the usvg crate.
pub struct Svg(pub usvg::Tree);
impl Svg {
/// Parse an SVG file from a data buffer. This also handles `.svgz`
/// compressed files.
pub fn parse(data: &[u8]) -> io::Result<Self> {
let usvg_opts = usvg::Options::default();
usvg::Tree::from_data(data, &usvg_opts.to_ref())
.map(Self)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
}
/// The width of the image in rounded-up nominal SVG pixels.
pub fn width(&self) -> u32 {
self.0.svg_node().size.width().ceil() as u32
}
/// The height of the image in rounded-up nominal SVG pixels.
pub fn height(&self) -> u32 {
self.0.svg_node().size.height().ceil() as u32
}
}
impl Debug for Svg {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Svg")
.field("width", &self.0.svg_node().size.width())
.field("height", &self.0.svg_node().size.height())
.field("viewBox", &self.0.svg_node().view_box)
.finish()
}
} }

View File

@ -57,7 +57,6 @@ use crate::diag::TypResult;
use crate::eval::Scope; use crate::eval::Scope;
use crate::font::FontStore; use crate::font::FontStore;
use crate::frame::Frame; use crate::frame::Frame;
use crate::image::ImageStore;
use crate::loading::Loader; use crate::loading::Loader;
use crate::model::StyleMap; use crate::model::StyleMap;
use crate::source::{SourceId, SourceStore}; use crate::source::{SourceId, SourceStore};
@ -80,8 +79,6 @@ pub struct Context {
pub sources: SourceStore, pub sources: SourceStore,
/// Stores parsed fonts. /// Stores parsed fonts.
pub fonts: FontStore, pub fonts: FontStore,
/// Stores decoded images.
pub images: ImageStore,
/// The context's configuration. /// The context's configuration.
config: Config, config: Config,
} }
@ -93,7 +90,6 @@ impl Context {
loader: Arc::clone(&loader), loader: Arc::clone(&loader),
sources: SourceStore::new(Arc::clone(&loader)), sources: SourceStore::new(Arc::clone(&loader)),
fonts: FontStore::new(Arc::clone(&loader)), fonts: FontStore::new(Arc::clone(&loader)),
images: ImageStore::new(loader),
config, config,
} }
} }

View File

@ -1,10 +1,12 @@
use crate::image::ImageId; use std::ffi::OsStr;
use crate::image::Image;
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::library::text::TextNode; use crate::library::text::TextNode;
/// Show a raster or vector graphic. /// Show a raster or vector graphic.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct ImageNode(pub ImageId); pub struct ImageNode(pub Image);
#[node] #[node]
impl ImageNode { impl ImageNode {
@ -16,12 +18,20 @@ impl ImageNode {
args.expect::<Spanned<EcoString>>("path to image file")?; args.expect::<Spanned<EcoString>>("path to image file")?;
let full = vm.locate(&path).at(span)?; let full = vm.locate(&path).at(span)?;
let id = vm.ctx.images.load(&full).at(span)?; let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default();
let image = vm
.ctx
.loader
.load(&full)
.and_then(|buffer| Image::new(buffer, ext))
.map_err(|err| failed_to_load("image", &full, err))
.at(span)?;
let width = args.named("width")?; let width = args.named("width")?;
let height = args.named("height")?; let height = args.named("height")?;
Ok(Content::inline( Ok(Content::inline(
ImageNode(id).pack().sized(Spec::new(width, height)), ImageNode(image).pack().sized(Spec::new(width, height)),
)) ))
} }
} }
@ -29,13 +39,12 @@ impl ImageNode {
impl Layout for ImageNode { impl Layout for ImageNode {
fn layout( fn layout(
&self, &self,
ctx: &mut Context, _: &mut Context,
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> TypResult<Vec<Frame>> { ) -> TypResult<Vec<Frame>> {
let img = ctx.images.get(self.0); let pxw = self.0.width() as f64;
let pxw = img.width() as f64; let pxh = self.0.height() as f64;
let pxh = img.height() as f64;
let px_ratio = pxw / pxh; let px_ratio = pxw / pxh;
// Find out whether the image is wider or taller than the target size. // Find out whether the image is wider or taller than the target size.
@ -71,7 +80,7 @@ impl Layout for ImageNode {
// the frame to the target size, center aligning the image in the // the frame to the target size, center aligning the image in the
// process. // process.
let mut frame = Frame::new(fitted); let mut frame = Frame::new(fitted);
frame.push(Point::zero(), Element::Image(self.0, fitted)); frame.push(Point::zero(), Element::Image(self.0.clone(), fitted));
frame.resize(target, Align::CENTER_HORIZON); frame.resize(target, Align::CENTER_HORIZON);
// Create a clipping group if only part of the image should be visible. // Create a clipping group if only part of the image should be visible.

View File

@ -25,8 +25,6 @@ pub enum ParChild {
Spacing(Spacing), Spacing(Spacing),
/// An arbitrary inline-level node. /// An arbitrary inline-level node.
Node(LayoutNode), Node(LayoutNode),
/// A pin identified by index.
Pin(usize),
} }
#[node] #[node]
@ -101,7 +99,6 @@ impl Debug for ParChild {
Self::Quote { double } => write!(f, "Quote({double})"), Self::Quote { double } => write!(f, "Quote({double})"),
Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Spacing(kind) => write!(f, "{:?}", kind),
Self::Node(node) => node.fmt(f), Self::Node(node) => node.fmt(f),
Self::Pin(idx) => write!(f, "Pin({idx})"),
} }
} }
} }
@ -197,7 +194,6 @@ type Range = std::ops::Range<usize>;
// paragraph's full text. // paragraph's full text.
const SPACING_REPLACE: char = ' '; // Space const SPACING_REPLACE: char = ' '; // Space
const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
const PIN_REPLACE: char = '\u{200D}'; // Zero Width Joiner
/// A paragraph representation in which children are already layouted and text /// A paragraph representation in which children are already layouted and text
/// is already preshaped. /// is already preshaped.
@ -278,8 +274,6 @@ enum Segment<'a> {
Spacing(Spacing), Spacing(Spacing),
/// An arbitrary inline-level layout node. /// An arbitrary inline-level layout node.
Node(&'a LayoutNode), Node(&'a LayoutNode),
/// A pin identified by index.
Pin(usize),
} }
impl Segment<'_> { impl Segment<'_> {
@ -289,7 +283,6 @@ impl Segment<'_> {
Self::Text(len) => len, Self::Text(len) => len,
Self::Spacing(_) => SPACING_REPLACE.len_utf8(), Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
Self::Node(_) => NODE_REPLACE.len_utf8(), Self::Node(_) => NODE_REPLACE.len_utf8(),
Self::Pin(_) => PIN_REPLACE.len_utf8(),
} }
} }
} }
@ -307,8 +300,6 @@ enum Item<'a> {
Frame(Frame), Frame(Frame),
/// A repeating node that fills the remaining space. /// A repeating node that fills the remaining space.
Repeat(&'a RepeatNode, StyleChain<'a>), Repeat(&'a RepeatNode, StyleChain<'a>),
/// A pin identified by index.
Pin(usize),
} }
impl<'a> Item<'a> { impl<'a> Item<'a> {
@ -326,7 +317,6 @@ impl<'a> Item<'a> {
Self::Text(shaped) => shaped.text.len(), Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(), Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(),
Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(), Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
Self::Pin(_) => PIN_REPLACE.len_utf8(),
} }
} }
@ -336,7 +326,7 @@ impl<'a> Item<'a> {
Self::Text(shaped) => shaped.width, Self::Text(shaped) => shaped.width,
Self::Absolute(v) => *v, Self::Absolute(v) => *v,
Self::Frame(frame) => frame.width(), Self::Frame(frame) => frame.width(),
Self::Fractional(_) | Self::Repeat(_, _) | Self::Pin(_) => Length::zero(), Self::Fractional(_) | Self::Repeat(_, _) => Length::zero(),
} }
} }
} }
@ -467,7 +457,6 @@ fn collect<'a>(
ParChild::Quote { .. } => Some('"'), ParChild::Quote { .. } => Some('"'),
ParChild::Spacing(_) => Some(SPACING_REPLACE), ParChild::Spacing(_) => Some(SPACING_REPLACE),
ParChild::Node(_) => Some(NODE_REPLACE), ParChild::Node(_) => Some(NODE_REPLACE),
ParChild::Pin(_) => Some(PIN_REPLACE),
}); });
full.push_str(quoter.quote(&quotes, double, peeked)); full.push_str(quoter.quote(&quotes, double, peeked));
@ -484,10 +473,6 @@ fn collect<'a>(
full.push(NODE_REPLACE); full.push(NODE_REPLACE);
Segment::Node(node) Segment::Node(node)
} }
&ParChild::Pin(idx) => {
full.push(PIN_REPLACE);
Segment::Pin(idx)
}
}; };
if let Some(last) = full.chars().last() { if let Some(last) = full.chars().last() {
@ -556,7 +541,6 @@ fn prepare<'a>(
items.push(Item::Frame(frame)); items.push(Item::Frame(frame));
} }
} }
Segment::Pin(idx) => items.push(Item::Pin(idx)),
} }
cursor = end; cursor = end;
@ -1187,11 +1171,6 @@ fn commit(
} }
offset = before + fill; offset = before + fill;
} }
Item::Pin(idx) => {
let mut frame = Frame::new(Size::zero());
frame.push(Point::zero(), Element::Pin(*idx));
push(&mut offset, frame);
}
} }
} }

View File

@ -6,7 +6,7 @@ use memmap2::Mmap;
use same_file::Handle; use same_file::Handle;
use walkdir::WalkDir; use walkdir::WalkDir;
use super::{FileHash, Loader}; use super::{Buffer, FileHash, Loader};
use crate::font::FontInfo; use crate::font::FontInfo;
/// Loads fonts and files from the local file system. /// Loads fonts and files from the local file system.
@ -130,7 +130,7 @@ impl Loader for FsLoader {
} }
} }
fn load(&self, path: &Path) -> io::Result<Vec<u8>> { fn load(&self, path: &Path) -> io::Result<Buffer> {
fs::read(path) Ok(fs::read(path)?.into())
} }
} }

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use super::{FileHash, Loader}; use super::{Buffer, FileHash, Loader};
use crate::font::FontInfo; use crate::font::FontInfo;
use crate::util::PathExt; use crate::util::PathExt;
@ -61,10 +61,10 @@ impl Loader for MemLoader {
} }
} }
fn load(&self, path: &Path) -> io::Result<Vec<u8>> { fn load(&self, path: &Path) -> io::Result<Buffer> {
self.files self.files
.get(&path.normalize()) .get(&path.normalize())
.map(|cow| cow.clone().into_owned()) .map(|cow| cow.clone().into_owned().into())
.ok_or_else(|| io::ErrorKind::NotFound.into()) .ok_or_else(|| io::ErrorKind::NotFound.into())
} }
} }
@ -90,7 +90,7 @@ mod tests {
// Test that the file can be loaded. // Test that the file can be loaded.
assert_eq!( assert_eq!(
loader.load(Path::new("directory/../PTSans.ttf")).unwrap(), loader.load(Path::new("directory/../PTSans.ttf")).unwrap().as_slice(),
data data
); );
} }

View File

@ -8,10 +8,14 @@ mod mem;
pub use fs::*; pub use fs::*;
pub use mem::*; pub use mem::*;
use std::fmt::{self, Debug, Formatter};
use std::io; use std::io;
use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use crate::font::FontInfo; use crate::font::FontInfo;
use crate::util::Prehashed;
/// A hash that identifies a file. /// A hash that identifies a file.
/// ///
@ -29,7 +33,7 @@ pub trait Loader {
fn resolve(&self, path: &Path) -> io::Result<FileHash>; fn resolve(&self, path: &Path) -> io::Result<FileHash>;
/// Load a file from a path. /// Load a file from a path.
fn load(&self, path: &Path) -> io::Result<Vec<u8>>; fn load(&self, path: &Path) -> io::Result<Buffer>;
} }
/// A loader which serves nothing. /// A loader which serves nothing.
@ -44,7 +48,61 @@ impl Loader for BlankLoader {
Err(io::ErrorKind::NotFound.into()) Err(io::ErrorKind::NotFound.into())
} }
fn load(&self, _: &Path) -> io::Result<Vec<u8>> { fn load(&self, _: &Path) -> io::Result<Buffer> {
Err(io::ErrorKind::NotFound.into()) Err(io::ErrorKind::NotFound.into())
} }
} }
/// A shared buffer that is cheap to clone.
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct Buffer(Prehashed<Arc<Vec<u8>>>);
impl Buffer {
/// Return a view into the buffer.
pub fn as_slice(&self) -> &[u8] {
self
}
/// Return a copy of the buffer as a vector.
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
}
impl From<&[u8]> for Buffer {
fn from(slice: &[u8]) -> Self {
Self(Prehashed::new(Arc::new(slice.to_vec())))
}
}
impl From<Vec<u8>> for Buffer {
fn from(vec: Vec<u8>) -> Self {
Self(Prehashed::new(Arc::new(vec)))
}
}
impl From<Arc<Vec<u8>>> for Buffer {
fn from(arc: Arc<Vec<u8>>) -> Self {
Self(Prehashed::new(arc))
}
}
impl Deref for Buffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<[u8]> for Buffer {
fn as_ref(&self) -> &[u8] {
self
}
}
impl Debug for Buffer {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("Buffer(..)")
}
}

View File

@ -90,8 +90,6 @@ pub enum Content {
/// A node that can be realized with styles, optionally with attached /// A node that can be realized with styles, optionally with attached
/// properties. /// properties.
Show(ShowNode, Option<Dict>), Show(ShowNode, Option<Dict>),
/// A pin identified by index.
Pin(usize),
/// Content with attached styles. /// Content with attached styles.
Styled(Arc<(Self, StyleMap)>), Styled(Arc<(Self, StyleMap)>),
/// A sequence of multiple nodes. /// A sequence of multiple nodes.
@ -281,7 +279,6 @@ impl Debug for Content {
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"), Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
Self::Page(page) => page.fmt(f), Self::Page(page) => page.fmt(f),
Self::Show(node, _) => node.fmt(f), Self::Show(node, _) => node.fmt(f),
Self::Pin(idx) => write!(f, "Pin({idx})"),
Self::Styled(styled) => { Self::Styled(styled) => {
let (sub, map) = styled.as_ref(); let (sub, map) = styled.as_ref();
map.fmt(f)?; map.fmt(f)?;
@ -651,9 +648,6 @@ impl<'a> ParBuilder<'a> {
Content::Inline(node) => { Content::Inline(node) => {
self.0.supportive(ParChild::Node(node.clone()), styles); self.0.supportive(ParChild::Node(node.clone()), styles);
} }
&Content::Pin(idx) => {
self.0.ignorant(ParChild::Pin(idx), styles);
}
_ => return false, _ => return false,
} }
@ -673,7 +667,7 @@ impl<'a> ParBuilder<'a> {
&& children && children
.items() .items()
.find_map(|child| match child { .find_map(|child| match child {
ParChild::Spacing(_) | ParChild::Pin(_) => None, ParChild::Spacing(_) => None,
ParChild::Text(_) | ParChild::Quote { .. } => Some(true), ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
ParChild::Node(_) => Some(false), ParChild::Node(_) => Some(false),
}) })

View File

@ -73,7 +73,7 @@ impl SourceStore {
/// root. /// root.
/// ///
/// If there already exists a source file for this path, it is /// If there already exists a source file for this path, it is
/// [replaced](SourceFile::replace). /// [replaced](Source::replace).
pub fn load(&mut self, path: &Path) -> StrResult<SourceId> { pub fn load(&mut self, path: &Path) -> StrResult<SourceId> {
let mut try_load = || -> io::Result<SourceId> { let mut try_load = || -> io::Result<SourceId> {
let hash = self.loader.resolve(path)?; let hash = self.loader.resolve(path)?;
@ -82,7 +82,7 @@ impl SourceStore {
} }
let data = self.loader.load(path)?; let data = self.loader.load(path)?;
let src = String::from_utf8(data).map_err(|_| { let src = String::from_utf8(data.to_vec()).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8")
})?; })?;
@ -99,7 +99,7 @@ impl SourceStore {
/// will use the inserted file instead of going through [`Loader::load`]. /// will use the inserted file instead of going through [`Loader::load`].
/// ///
/// If the path is resolvable and points to an existing source file, it is /// If the path is resolvable and points to an existing source file, it is
/// [replaced](SourceFile::replace). /// [replaced](Source::replace).
pub fn provide(&mut self, path: impl AsRef<Path>, src: String) -> SourceId { pub fn provide(&mut self, path: impl AsRef<Path>, src: String) -> SourceId {
let path = path.as_ref(); let path = path.as_ref();
let hash = self.loader.resolve(path).ok(); let hash = self.loader.resolve(path).ok();
@ -122,7 +122,7 @@ impl SourceStore {
id id
} }
/// Fully [replace](SourceFile::replace) the source text of a file. /// Fully [replace](Source::replace) the source text of a file.
/// ///
/// This panics if no source file with this `id` exists. /// This panics if no source file with this `id` exists.
#[track_caller] #[track_caller]
@ -130,7 +130,7 @@ impl SourceStore {
self.sources[id.0 as usize].replace(src) self.sources[id.0 as usize].replace(src)
} }
/// [Edit](SourceFile::edit) a source file by replacing the given range. /// [Edit](Source::edit) a source file by replacing the given range.
/// ///
/// This panics if no source file with this `id` exists or if the `replace` /// This panics if no source file with this `id` exists or if the `replace`
/// range is out of bounds. /// range is out of bounds.
@ -144,7 +144,7 @@ impl SourceStore {
self.sources[id.0 as usize].edit(replace, with) self.sources[id.0 as usize].edit(replace, with)
} }
/// Map a span that points into a [file](SourceFile::range) stored in this /// Map a span that points into a [file](Source::range) stored in this
/// source store to a byte range. /// source store to a byte range.
/// ///
/// Panics if the span does not point into this source store. /// Panics if the span does not point into this source store.