Remove image store
This commit is contained in:
parent
30be75c668
commit
59f67b79c7
@ -9,7 +9,7 @@ use crate::util::SliceExt;
|
||||
|
||||
/// Embed all used fonts into the PDF.
|
||||
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 cid_ref = ctx.alloc.bump();
|
||||
let descriptor_ref = ctx.alloc.bump();
|
||||
|
@ -1,25 +1,26 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
|
||||
use image::{DynamicImage, GenericImageView, ImageResult, Rgba};
|
||||
use pdf_writer::{Filter, Finish};
|
||||
|
||||
use super::{deflate, PdfContext, RefExt};
|
||||
use crate::image::{Image, RasterImage};
|
||||
use crate::image::{DecodedImage, ImageFormat};
|
||||
|
||||
/// Embed all used images into the PDF.
|
||||
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();
|
||||
ctx.image_refs.push(image_ref);
|
||||
|
||||
let img = ctx.images.get(image_id);
|
||||
let width = img.width();
|
||||
let height = img.height();
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
|
||||
// Add the primary image.
|
||||
match img {
|
||||
Image::Raster(img) => {
|
||||
if let Ok((data, filter, has_color)) = encode_image(img) {
|
||||
match image.decode().unwrap() {
|
||||
DecodedImage::Raster(dynamic) => {
|
||||
if let Ok((data, filter, has_color)) =
|
||||
encode_image(image.format(), &dynamic)
|
||||
{
|
||||
let mut image = ctx.writer.image_xobject(image_ref, &data);
|
||||
image.filter(filter);
|
||||
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
|
||||
// this image has an alpha channel.
|
||||
if img.buf.color().has_alpha() {
|
||||
let (alpha_data, alpha_filter) = encode_alpha(img);
|
||||
if dynamic.color().has_alpha() {
|
||||
let (alpha_data, alpha_filter) = encode_alpha(&dynamic);
|
||||
let mask_ref = ctx.alloc.bump();
|
||||
image.s_mask(mask_ref);
|
||||
image.finish();
|
||||
@ -59,9 +60,9 @@ pub fn write_images(ctx: &mut PdfContext) {
|
||||
.device_gray();
|
||||
}
|
||||
}
|
||||
Image::Svg(img) => {
|
||||
DecodedImage::Svg(svg) => {
|
||||
let next_ref = svg2pdf::convert_tree_into(
|
||||
&img.0,
|
||||
&svg,
|
||||
svg2pdf::Options::default(),
|
||||
&mut ctx.writer,
|
||||
image_ref,
|
||||
@ -76,19 +77,22 @@ pub fn write_images(ctx: &mut PdfContext) {
|
||||
/// whether the image has color.
|
||||
///
|
||||
/// Skips the alpha channel as that's encoded separately.
|
||||
fn encode_image(img: &RasterImage) -> ImageResult<(Vec<u8>, Filter, bool)> {
|
||||
Ok(match (img.format, &img.buf) {
|
||||
fn encode_image(
|
||||
format: ImageFormat,
|
||||
dynamic: &DynamicImage,
|
||||
) -> ImageResult<(Vec<u8>, Filter, bool)> {
|
||||
Ok(match (format, dynamic) {
|
||||
// 8-bit gray JPEG.
|
||||
(ImageFormat::Jpeg, DynamicImage::ImageLuma8(_)) => {
|
||||
(ImageFormat::Jpg, DynamicImage::ImageLuma8(_)) => {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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![]);
|
||||
img.buf.write_to(&mut data, img.format)?;
|
||||
dynamic.write_to(&mut data, image::ImageFormat::Jpeg)?;
|
||||
(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.
|
||||
fn encode_alpha(img: &RasterImage) -> (Vec<u8>, Filter) {
|
||||
let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
|
||||
fn encode_alpha(dynamic: &DynamicImage) -> (Vec<u8>, Filter) {
|
||||
let pixels: Vec<_> = dynamic.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
|
||||
(deflate(&pixels), Filter::FlateDecode)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ use self::page::Page;
|
||||
use crate::font::{FontId, FontStore};
|
||||
use crate::frame::Frame;
|
||||
use crate::geom::{Dir, Em, Length};
|
||||
use crate::image::{ImageId, ImageStore};
|
||||
use crate::image::Image;
|
||||
use crate::library::text::Lang;
|
||||
use crate::Context;
|
||||
|
||||
@ -46,7 +46,6 @@ const D65_GRAY: Name<'static> = Name(b"d65gray");
|
||||
pub struct PdfContext<'a> {
|
||||
writer: PdfWriter,
|
||||
fonts: &'a FontStore,
|
||||
images: &'a ImageStore,
|
||||
pages: Vec<Page>,
|
||||
page_heights: Vec<f32>,
|
||||
alloc: Ref,
|
||||
@ -55,7 +54,7 @@ pub struct PdfContext<'a> {
|
||||
image_refs: Vec<Ref>,
|
||||
page_refs: Vec<Ref>,
|
||||
font_map: Remapper<FontId>,
|
||||
image_map: Remapper<ImageId>,
|
||||
image_map: Remapper<Image>,
|
||||
glyph_sets: HashMap<FontId, HashSet<u16>>,
|
||||
languages: HashMap<Lang, usize>,
|
||||
heading_tree: Vec<HeadingNode>,
|
||||
@ -68,7 +67,6 @@ impl<'a> PdfContext<'a> {
|
||||
Self {
|
||||
writer: PdfWriter::new(),
|
||||
fonts: &ctx.fonts,
|
||||
images: &ctx.images,
|
||||
pages: vec![],
|
||||
page_heights: vec![],
|
||||
alloc,
|
||||
@ -147,36 +145,33 @@ fn deflate(data: &[u8]) -> Vec<u8> {
|
||||
miniz_oxide::deflate::compress_to_vec_zlib(data, COMPRESSION_LEVEL)
|
||||
}
|
||||
|
||||
/// Assigns new, consecutive PDF-internal indices to things.
|
||||
struct Remapper<Index> {
|
||||
/// Forwards from the old indices to the new pdf indices.
|
||||
to_pdf: HashMap<Index, usize>,
|
||||
/// Backwards from the pdf indices to the old indices.
|
||||
to_layout: Vec<Index>,
|
||||
/// Assigns new, consecutive PDF-internal indices to items.
|
||||
struct Remapper<T> {
|
||||
/// Forwards from the items to the pdf indices.
|
||||
to_pdf: HashMap<T, usize>,
|
||||
/// Backwards from the pdf indices to the items.
|
||||
to_items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<Index> Remapper<Index>
|
||||
impl<T> Remapper<T>
|
||||
where
|
||||
Index: Copy + Eq + Hash,
|
||||
T: Eq + Hash + Clone,
|
||||
{
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
to_pdf: HashMap::new(),
|
||||
to_layout: vec![],
|
||||
}
|
||||
Self { to_pdf: HashMap::new(), to_items: vec![] }
|
||||
}
|
||||
|
||||
fn insert(&mut self, index: Index) {
|
||||
let to_layout = &mut self.to_layout;
|
||||
self.to_pdf.entry(index).or_insert_with(|| {
|
||||
fn insert(&mut self, item: T) {
|
||||
let to_layout = &mut self.to_items;
|
||||
self.to_pdf.entry(item.clone()).or_insert_with(|| {
|
||||
let pdf_index = to_layout.len();
|
||||
to_layout.push(index);
|
||||
to_layout.push(item);
|
||||
pdf_index
|
||||
});
|
||||
}
|
||||
|
||||
fn map(&self, index: Index) -> usize {
|
||||
self.to_pdf[&index]
|
||||
fn map(&self, item: T) -> usize {
|
||||
self.to_pdf[&item]
|
||||
}
|
||||
|
||||
fn pdf_indices<'a>(
|
||||
@ -186,8 +181,8 @@ where
|
||||
refs.iter().copied().zip(0 .. self.to_pdf.len())
|
||||
}
|
||||
|
||||
fn layout_indices(&self) -> impl Iterator<Item = Index> + '_ {
|
||||
self.to_layout.iter().copied()
|
||||
fn items(&self) -> impl Iterator<Item = &T> + '_ {
|
||||
self.to_items.iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ use crate::geom::{
|
||||
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
||||
Transform,
|
||||
};
|
||||
use crate::image::ImageId;
|
||||
use crate::image::Image;
|
||||
|
||||
/// Construct page objects.
|
||||
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() {
|
||||
let x = pos.x.to_f32();
|
||||
let y = pos.y.to_f32();
|
||||
match *element {
|
||||
Element::Group(ref group) => write_group(ctx, pos, group),
|
||||
Element::Text(ref text) => write_text(ctx, x, y, text),
|
||||
Element::Shape(ref shape) => write_shape(ctx, x, y, shape),
|
||||
Element::Image(id, size) => write_image(ctx, x, y, id, size),
|
||||
Element::Link(ref dest, size) => write_link(ctx, pos, dest, size),
|
||||
Element::Pin(_) => {}
|
||||
match element {
|
||||
Element::Group(group) => write_group(ctx, pos, group),
|
||||
Element::Text(text) => write_text(ctx, x, y, text),
|
||||
Element::Shape(shape) => write_shape(ctx, x, y, shape),
|
||||
Element::Image(image, size) => write_image(ctx, x, y, image, *size),
|
||||
Element::Link(dest, size) => write_link(ctx, pos, dest, *size),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
fn write_image(ctx: &mut PageContext, x: f32, y: f32, id: ImageId, size: Size) {
|
||||
ctx.parent.image_map.insert(id);
|
||||
let name = format_eco!("Im{}", ctx.parent.image_map.map(id));
|
||||
fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) {
|
||||
ctx.parent.image_map.insert(image.clone());
|
||||
let name = format_eco!("Im{}", ctx.parent.image_map.map(image.clone()));
|
||||
let w = size.x.to_f32();
|
||||
let h = size.y.to_f32();
|
||||
ctx.content.save_state();
|
||||
|
@ -12,7 +12,7 @@ use crate::frame::{Element, Frame, Group, Text};
|
||||
use crate::geom::{
|
||||
self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform,
|
||||
};
|
||||
use crate::image::{Image, RasterImage, Svg};
|
||||
use crate::image::{DecodedImage, Image};
|
||||
use crate::Context;
|
||||
|
||||
/// Export a frame into a rendered image.
|
||||
@ -49,21 +49,20 @@ fn render_frame(
|
||||
let y = pos.y.to_f32();
|
||||
let ts = ts.pre_translate(x, y);
|
||||
|
||||
match *element {
|
||||
Element::Group(ref group) => {
|
||||
match element {
|
||||
Element::Group(group) => {
|
||||
render_group(canvas, ts, mask, ctx, group);
|
||||
}
|
||||
Element::Text(ref text) => {
|
||||
Element::Text(text) => {
|
||||
render_text(canvas, ts, mask, ctx, text);
|
||||
}
|
||||
Element::Shape(ref shape) => {
|
||||
Element::Shape(shape) => {
|
||||
render_shape(canvas, ts, mask, shape);
|
||||
}
|
||||
Element::Image(id, size) => {
|
||||
render_image(canvas, ts, mask, ctx.images.get(id), size);
|
||||
Element::Image(image, size) => {
|
||||
render_image(canvas, ts, mask, image, *size);
|
||||
}
|
||||
Element::Link(_, _) => {}
|
||||
Element::Pin(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,17 +196,20 @@ fn render_bitmap_glyph(
|
||||
let ppem = size * ts.sy;
|
||||
let font = ctx.fonts.get(text.font_id);
|
||||
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,
|
||||
// and maybe also for Noto Color Emoji. And: Is the size calculation
|
||||
// correct?
|
||||
let h = text.size;
|
||||
let w = (img.width() as f64 / img.height() as f64) * h;
|
||||
let dx = (raster.x as f32) / (img.width() as f32) * size;
|
||||
let dy = (raster.y as f32) / (img.height() as f32) * size;
|
||||
let w = (image.width() as f64 / image.height() as f64) * h;
|
||||
let dx = (raster.x as f32) / (image.width() as f32) * size;
|
||||
let dy = (raster.y as f32) / (image.height() as f32) * size;
|
||||
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.
|
||||
@ -338,33 +340,33 @@ fn render_image(
|
||||
canvas: &mut sk::Pixmap,
|
||||
ts: sk::Transform,
|
||||
mask: Option<&sk::ClipMask>,
|
||||
img: &Image,
|
||||
image: &Image,
|
||||
size: Size,
|
||||
) -> Option<()> {
|
||||
let view_width = size.x.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 w = (scale * view_width.max(aspect * view_height)).ceil() as u32;
|
||||
let h = ((w as f32) / aspect).ceil() as u32;
|
||||
|
||||
let mut pixmap = sk::Pixmap::new(w, h)?;
|
||||
match img {
|
||||
Image::Raster(img) => {
|
||||
let downscale = w < img.width();
|
||||
match image.decode().unwrap() {
|
||||
DecodedImage::Raster(dynamic) => {
|
||||
let downscale = w < image.width();
|
||||
let filter = if downscale {
|
||||
FilterType::Lanczos3
|
||||
} else {
|
||||
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()) {
|
||||
let Rgba([r, g, b, a]) = src;
|
||||
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
|
||||
}
|
||||
}
|
||||
Image::Svg(Svg(tree)) => {
|
||||
DecodedImage::Svg(tree) => {
|
||||
resvg::render(
|
||||
&tree,
|
||||
FitTo::Size(w, h),
|
||||
|
22
src/font.rs
22
src/font.rs
@ -13,7 +13,7 @@ use ttf_parser::{name_id, GlyphId, PlatformId, Tag};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::geom::Em;
|
||||
use crate::loading::{FileHash, Loader};
|
||||
use crate::loading::{Buffer, FileHash, Loader};
|
||||
|
||||
/// A unique identifier for a loaded font.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
@ -40,7 +40,7 @@ pub struct FontStore {
|
||||
failed: Vec<bool>,
|
||||
fonts: Vec<Option<Font>>,
|
||||
families: BTreeMap<String, Vec<FontId>>,
|
||||
buffers: HashMap<FileHash, Arc<Vec<u8>>>,
|
||||
buffers: HashMap<FileHash, Buffer>,
|
||||
}
|
||||
|
||||
impl FontStore {
|
||||
@ -214,11 +214,11 @@ impl FontStore {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
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);
|
||||
self.failed[idx] = false;
|
||||
|
||||
@ -239,7 +239,7 @@ pub struct Font {
|
||||
/// The raw font data, possibly shared with other fonts from the same
|
||||
/// collection. The vector's allocation must not move, because `ttf` points
|
||||
/// into it using unsafe code.
|
||||
buffer: Arc<Vec<u8>>,
|
||||
data: Buffer,
|
||||
/// The font's index in the collection (zero if not a collection).
|
||||
index: u32,
|
||||
/// The underlying ttf-parser/rustybuzz face.
|
||||
@ -251,8 +251,8 @@ pub struct Font {
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Parse a font from a buffer and collection index.
|
||||
pub fn new(buffer: Arc<Vec<u8>>, index: u32) -> Option<Self> {
|
||||
/// Parse a font from data and collection index.
|
||||
pub fn new(data: Buffer, index: u32) -> Option<Self> {
|
||||
// Safety:
|
||||
// - The slices's location is stable in memory:
|
||||
// - We don't move the underlying vector
|
||||
@ -260,13 +260,13 @@ impl Font {
|
||||
// - The internal 'static lifetime is not leaked because its rewritten
|
||||
// to the self-lifetime in `ttf()`.
|
||||
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 metrics = FontMetrics::from_ttf(&ttf);
|
||||
|
||||
Some(Self {
|
||||
buffer,
|
||||
data,
|
||||
index,
|
||||
ttf,
|
||||
metrics,
|
||||
@ -275,8 +275,8 @@ impl Font {
|
||||
}
|
||||
|
||||
/// The underlying buffer.
|
||||
pub fn buffer(&self) -> &Arc<Vec<u8>> {
|
||||
&self.buffer
|
||||
pub fn buffer(&self) -> &Buffer {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// The collection index.
|
||||
|
@ -9,7 +9,7 @@ use crate::font::FontId;
|
||||
use crate::geom::{
|
||||
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::util::EcoString;
|
||||
|
||||
@ -303,12 +303,9 @@ pub enum Element {
|
||||
/// A geometric shape with optional fill and stroke.
|
||||
Shape(Shape),
|
||||
/// An image and its size.
|
||||
Image(ImageId, Size),
|
||||
Image(Image, Size),
|
||||
/// A link to an external resource and its trigger region.
|
||||
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 {
|
||||
@ -319,7 +316,6 @@ impl Debug for Element {
|
||||
Self::Shape(shape) => write!(f, "{shape:?}"),
|
||||
Self::Image(image, _) => write!(f, "{image:?}"),
|
||||
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
|
||||
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
294
src/image.rs
294
src/image.rs
@ -1,213 +1,139 @@
|
||||
//! Image handling.
|
||||
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{DynamicImage, ImageFormat};
|
||||
use crate::loading::Buffer;
|
||||
|
||||
use crate::diag::{failed_to_load, StrResult};
|
||||
use crate::loading::{FileHash, Loader};
|
||||
|
||||
/// A unique identifier for a loaded image.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ImageId(u32);
|
||||
|
||||
impl ImageId {
|
||||
/// Create an image id from the raw underlying value.
|
||||
///
|
||||
/// This should only be called with values returned by
|
||||
/// [`into_raw`](Self::into_raw).
|
||||
pub const fn from_raw(v: u32) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
/// Convert into the raw underlying value.
|
||||
pub const fn into_raw(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
/// A raster or vector image.
|
||||
///
|
||||
/// Values of this type are cheap to clone and hash.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Image {
|
||||
/// The raw, undecoded image data.
|
||||
data: Buffer,
|
||||
/// The format of the encoded `buffer`.
|
||||
format: ImageFormat,
|
||||
/// The width in pixels.
|
||||
width: u32,
|
||||
/// The height in pixels.
|
||||
height: u32,
|
||||
}
|
||||
|
||||
/// Storage for loaded and decoded images.
|
||||
pub struct ImageStore {
|
||||
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 decoded image.
|
||||
pub enum DecodedImage {
|
||||
/// A pixel raster format, like PNG or JPEG.
|
||||
Raster(RasterImage),
|
||||
Raster(image::DynamicImage),
|
||||
/// 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 {
|
||||
/// Parse an image from raw data. The file extension is used as a hint for
|
||||
/// which error message describes the problem best.
|
||||
pub fn parse(data: &[u8], ext: &str) -> io::Result<Self> {
|
||||
match Svg::parse(data) {
|
||||
Ok(svg) => return Ok(Self::Svg(svg)),
|
||||
Err(err) if matches!(ext, "svg" | "svgz") => return Err(err),
|
||||
Err(_) => {}
|
||||
}
|
||||
/// Create an image from a raw buffer and a file extension.
|
||||
///
|
||||
/// The file extension is used to determine the format.
|
||||
pub fn new(data: Buffer, ext: &str) -> io::Result<Self> {
|
||||
let format = match ext {
|
||||
"svg" | "svgz" => ImageFormat::Svg,
|
||||
"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) {
|
||||
Ok(raster) => return Ok(Self::Raster(raster)),
|
||||
Err(err) if matches!(ext, "png" | "jpg" | "jpeg" | "gif") => return Err(err),
|
||||
Err(_) => {}
|
||||
}
|
||||
let (width, height) = match format {
|
||||
ImageFormat::Svg => {
|
||||
let opts = usvg::Options::default();
|
||||
let tree =
|
||||
usvg::Tree::from_data(&data, &opts.to_ref()).map_err(invalid)?;
|
||||
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"unknown image format",
|
||||
))
|
||||
let size = tree.svg_node().size;
|
||||
let width = size.width().ceil() as u32;
|
||||
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.
|
||||
pub fn width(&self) -> u32 {
|
||||
match self {
|
||||
Self::Raster(image) => image.width(),
|
||||
Self::Svg(image) => image.width(),
|
||||
}
|
||||
self.width
|
||||
}
|
||||
|
||||
/// The height of the image in pixels.
|
||||
pub fn height(&self) -> u32 {
|
||||
match self {
|
||||
Self::Raster(image) => image.height(),
|
||||
Self::Svg(image) => image.height(),
|
||||
}
|
||||
self.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.
|
||||
pub struct RasterImage {
|
||||
/// The original format the image was encoded in.
|
||||
pub format: ImageFormat,
|
||||
/// The decoded image.
|
||||
pub buf: DynamicImage,
|
||||
}
|
||||
|
||||
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()
|
||||
/// Convert a raster image format to the image crate's format.
|
||||
fn convert_format(format: ImageFormat) -> image::ImageFormat {
|
||||
match format {
|
||||
ImageFormat::Png => image::ImageFormat::Png,
|
||||
ImageFormat::Jpg => image::ImageFormat::Jpeg,
|
||||
ImageFormat::Gif => image::ImageFormat::Gif,
|
||||
ImageFormat::Svg => panic!("must be a raster format"),
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RasterImage {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("Image")
|
||||
.field("format", &self.format)
|
||||
.field("color", &self.buf.color())
|
||||
.field("width", &self.width())
|
||||
.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()
|
||||
}
|
||||
/// Turn any error into an I/O error.
|
||||
fn invalid<E>(error: E) -> io::Error
|
||||
where
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
io::Error::new(io::ErrorKind::InvalidData, error)
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ use crate::diag::TypResult;
|
||||
use crate::eval::Scope;
|
||||
use crate::font::FontStore;
|
||||
use crate::frame::Frame;
|
||||
use crate::image::ImageStore;
|
||||
use crate::loading::Loader;
|
||||
use crate::model::StyleMap;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
@ -80,8 +79,6 @@ pub struct Context {
|
||||
pub sources: SourceStore,
|
||||
/// Stores parsed fonts.
|
||||
pub fonts: FontStore,
|
||||
/// Stores decoded images.
|
||||
pub images: ImageStore,
|
||||
/// The context's configuration.
|
||||
config: Config,
|
||||
}
|
||||
@ -93,7 +90,6 @@ impl Context {
|
||||
loader: Arc::clone(&loader),
|
||||
sources: SourceStore::new(Arc::clone(&loader)),
|
||||
fonts: FontStore::new(Arc::clone(&loader)),
|
||||
images: ImageStore::new(loader),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
use crate::image::ImageId;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use crate::image::Image;
|
||||
use crate::library::prelude::*;
|
||||
use crate::library::text::TextNode;
|
||||
|
||||
/// Show a raster or vector graphic.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ImageNode(pub ImageId);
|
||||
pub struct ImageNode(pub Image);
|
||||
|
||||
#[node]
|
||||
impl ImageNode {
|
||||
@ -16,12 +18,20 @@ impl ImageNode {
|
||||
args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||
|
||||
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 height = args.named("height")?;
|
||||
|
||||
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 {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut Context,
|
||||
_: &mut Context,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> TypResult<Vec<Frame>> {
|
||||
let img = ctx.images.get(self.0);
|
||||
let pxw = img.width() as f64;
|
||||
let pxh = img.height() as f64;
|
||||
let pxw = self.0.width() as f64;
|
||||
let pxh = self.0.height() as f64;
|
||||
let px_ratio = pxw / pxh;
|
||||
|
||||
// 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
|
||||
// process.
|
||||
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);
|
||||
|
||||
// Create a clipping group if only part of the image should be visible.
|
||||
|
@ -25,8 +25,6 @@ pub enum ParChild {
|
||||
Spacing(Spacing),
|
||||
/// An arbitrary inline-level node.
|
||||
Node(LayoutNode),
|
||||
/// A pin identified by index.
|
||||
Pin(usize),
|
||||
}
|
||||
|
||||
#[node]
|
||||
@ -101,7 +99,6 @@ impl Debug for ParChild {
|
||||
Self::Quote { double } => write!(f, "Quote({double})"),
|
||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||
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.
|
||||
const SPACING_REPLACE: char = ' '; // Space
|
||||
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
|
||||
/// is already preshaped.
|
||||
@ -278,8 +274,6 @@ enum Segment<'a> {
|
||||
Spacing(Spacing),
|
||||
/// An arbitrary inline-level layout node.
|
||||
Node(&'a LayoutNode),
|
||||
/// A pin identified by index.
|
||||
Pin(usize),
|
||||
}
|
||||
|
||||
impl Segment<'_> {
|
||||
@ -289,7 +283,6 @@ impl Segment<'_> {
|
||||
Self::Text(len) => len,
|
||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||
Self::Node(_) => NODE_REPLACE.len_utf8(),
|
||||
Self::Pin(_) => PIN_REPLACE.len_utf8(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,8 +300,6 @@ enum Item<'a> {
|
||||
Frame(Frame),
|
||||
/// A repeating node that fills the remaining space.
|
||||
Repeat(&'a RepeatNode, StyleChain<'a>),
|
||||
/// A pin identified by index.
|
||||
Pin(usize),
|
||||
}
|
||||
|
||||
impl<'a> Item<'a> {
|
||||
@ -326,7 +317,6 @@ impl<'a> Item<'a> {
|
||||
Self::Text(shaped) => shaped.text.len(),
|
||||
Self::Absolute(_) | Self::Fractional(_) => SPACING_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::Absolute(v) => *v,
|
||||
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::Spacing(_) => Some(SPACING_REPLACE),
|
||||
ParChild::Node(_) => Some(NODE_REPLACE),
|
||||
ParChild::Pin(_) => Some(PIN_REPLACE),
|
||||
});
|
||||
|
||||
full.push_str(quoter.quote("es, double, peeked));
|
||||
@ -484,10 +473,6 @@ fn collect<'a>(
|
||||
full.push(NODE_REPLACE);
|
||||
Segment::Node(node)
|
||||
}
|
||||
&ParChild::Pin(idx) => {
|
||||
full.push(PIN_REPLACE);
|
||||
Segment::Pin(idx)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(last) = full.chars().last() {
|
||||
@ -556,7 +541,6 @@ fn prepare<'a>(
|
||||
items.push(Item::Frame(frame));
|
||||
}
|
||||
}
|
||||
Segment::Pin(idx) => items.push(Item::Pin(idx)),
|
||||
}
|
||||
|
||||
cursor = end;
|
||||
@ -1187,11 +1171,6 @@ fn commit(
|
||||
}
|
||||
offset = before + fill;
|
||||
}
|
||||
Item::Pin(idx) => {
|
||||
let mut frame = Frame::new(Size::zero());
|
||||
frame.push(Point::zero(), Element::Pin(*idx));
|
||||
push(&mut offset, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use memmap2::Mmap;
|
||||
use same_file::Handle;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::{FileHash, Loader};
|
||||
use super::{Buffer, FileHash, Loader};
|
||||
use crate::font::FontInfo;
|
||||
|
||||
/// 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>> {
|
||||
fs::read(path)
|
||||
fn load(&self, path: &Path) -> io::Result<Buffer> {
|
||||
Ok(fs::read(path)?.into())
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{FileHash, Loader};
|
||||
use super::{Buffer, FileHash, Loader};
|
||||
use crate::font::FontInfo;
|
||||
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
|
||||
.get(&path.normalize())
|
||||
.map(|cow| cow.clone().into_owned())
|
||||
.map(|cow| cow.clone().into_owned().into())
|
||||
.ok_or_else(|| io::ErrorKind::NotFound.into())
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ mod tests {
|
||||
|
||||
// Test that the file can be loaded.
|
||||
assert_eq!(
|
||||
loader.load(Path::new("directory/../PTSans.ttf")).unwrap(),
|
||||
loader.load(Path::new("directory/../PTSans.ttf")).unwrap().as_slice(),
|
||||
data
|
||||
);
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ mod mem;
|
||||
pub use fs::*;
|
||||
pub use mem::*;
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::font::FontInfo;
|
||||
use crate::util::Prehashed;
|
||||
|
||||
/// A hash that identifies a file.
|
||||
///
|
||||
@ -29,7 +33,7 @@ pub trait Loader {
|
||||
fn resolve(&self, path: &Path) -> io::Result<FileHash>;
|
||||
|
||||
/// 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.
|
||||
@ -44,7 +48,61 @@ impl Loader for BlankLoader {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(..)")
|
||||
}
|
||||
}
|
||||
|
@ -90,8 +90,6 @@ pub enum Content {
|
||||
/// A node that can be realized with styles, optionally with attached
|
||||
/// properties.
|
||||
Show(ShowNode, Option<Dict>),
|
||||
/// A pin identified by index.
|
||||
Pin(usize),
|
||||
/// Content with attached styles.
|
||||
Styled(Arc<(Self, StyleMap)>),
|
||||
/// A sequence of multiple nodes.
|
||||
@ -281,7 +279,6 @@ impl Debug for Content {
|
||||
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
|
||||
Self::Page(page) => page.fmt(f),
|
||||
Self::Show(node, _) => node.fmt(f),
|
||||
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||
Self::Styled(styled) => {
|
||||
let (sub, map) = styled.as_ref();
|
||||
map.fmt(f)?;
|
||||
@ -651,9 +648,6 @@ impl<'a> ParBuilder<'a> {
|
||||
Content::Inline(node) => {
|
||||
self.0.supportive(ParChild::Node(node.clone()), styles);
|
||||
}
|
||||
&Content::Pin(idx) => {
|
||||
self.0.ignorant(ParChild::Pin(idx), styles);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
@ -673,7 +667,7 @@ impl<'a> ParBuilder<'a> {
|
||||
&& children
|
||||
.items()
|
||||
.find_map(|child| match child {
|
||||
ParChild::Spacing(_) | ParChild::Pin(_) => None,
|
||||
ParChild::Spacing(_) => None,
|
||||
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
||||
ParChild::Node(_) => Some(false),
|
||||
})
|
||||
|
@ -73,7 +73,7 @@ impl SourceStore {
|
||||
/// root.
|
||||
///
|
||||
/// 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> {
|
||||
let mut try_load = || -> io::Result<SourceId> {
|
||||
let hash = self.loader.resolve(path)?;
|
||||
@ -82,7 +82,7 @@ impl SourceStore {
|
||||
}
|
||||
|
||||
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")
|
||||
})?;
|
||||
|
||||
@ -99,7 +99,7 @@ impl SourceStore {
|
||||
/// 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
|
||||
/// [replaced](SourceFile::replace).
|
||||
/// [replaced](Source::replace).
|
||||
pub fn provide(&mut self, path: impl AsRef<Path>, src: String) -> SourceId {
|
||||
let path = path.as_ref();
|
||||
let hash = self.loader.resolve(path).ok();
|
||||
@ -122,7 +122,7 @@ impl SourceStore {
|
||||
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.
|
||||
#[track_caller]
|
||||
@ -130,7 +130,7 @@ impl SourceStore {
|
||||
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`
|
||||
/// range is out of bounds.
|
||||
@ -144,7 +144,7 @@ impl SourceStore {
|
||||
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.
|
||||
///
|
||||
/// Panics if the span does not point into this source store.
|
||||
|
Loading…
x
Reference in New Issue
Block a user