mirror of
https://github.com/typst/typst
synced 2025-06-08 13:16:24 +08:00
Add proper error conversion
This commit is contained in:
parent
6761cbc622
commit
5780432039
@ -1,4 +1,4 @@
|
|||||||
use crate::primitive::{AbsExt, PointExt, SizeExt, TransformExt};
|
use crate::util::{font_to_str, AbsExt, PointExt, SizeExt, TransformExt};
|
||||||
use crate::{paint, PdfOptions};
|
use crate::{paint, PdfOptions};
|
||||||
use bytemuck::TransparentWrapper;
|
use bytemuck::TransparentWrapper;
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
@ -12,7 +12,9 @@ use krilla::{PageSettings, SerializeSettings, SvgSettings};
|
|||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use krilla::error::KrillaError;
|
||||||
use krilla::geom::Rect;
|
use krilla::geom::Rect;
|
||||||
|
use krilla::validation::ValidationError;
|
||||||
use typst_library::diag::{bail, SourceResult};
|
use typst_library::diag::{bail, SourceResult};
|
||||||
use typst_library::foundations::Datetime;
|
use typst_library::foundations::Datetime;
|
||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
@ -150,7 +152,8 @@ impl krilla::font::Glyph for PdfGlyph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct GlobalContext {
|
pub struct GlobalContext {
|
||||||
fonts: HashMap<Font, krilla::font::Font>,
|
fonts_forward: HashMap<Font, krilla::font::Font>,
|
||||||
|
fonts_backward: HashMap<krilla::font::Font, Font>,
|
||||||
// Note: In theory, the same image can have multiple spans
|
// Note: In theory, the same image can have multiple spans
|
||||||
// if it appears in the document multiple times. We just store the
|
// if it appears in the document multiple times. We just store the
|
||||||
// first appearance, though.
|
// first appearance, though.
|
||||||
@ -161,7 +164,8 @@ pub struct GlobalContext {
|
|||||||
impl GlobalContext {
|
impl GlobalContext {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
fonts: HashMap::new(),
|
fonts_forward: HashMap::new(),
|
||||||
|
fonts_backward: HashMap::new(),
|
||||||
image_spans: HashMap::new(),
|
image_spans: HashMap::new(),
|
||||||
languages: BTreeMap::new(),
|
languages: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
@ -279,7 +283,111 @@ pub fn pdf(
|
|||||||
|
|
||||||
document.set_metadata(metadata);
|
document.set_metadata(metadata);
|
||||||
|
|
||||||
Ok(document.finish().unwrap())
|
match document.finish() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => match e {
|
||||||
|
KrillaError::FontError(f, s) => {
|
||||||
|
let font_str = font_to_str(gc.fonts_backward.get(&f).unwrap());
|
||||||
|
bail!(Span::detached(), "failed to process font {font_str} ({s})");
|
||||||
|
}
|
||||||
|
KrillaError::UserError(u) => {
|
||||||
|
// This is an error which indicates misuse on the typst-pdf side.
|
||||||
|
bail!(Span::detached(), "internal error ({u})"; hint: "please report this as a bug")
|
||||||
|
}
|
||||||
|
KrillaError::ValidationError(ve) => {
|
||||||
|
// We can only produce 1 error, so just take the first one.
|
||||||
|
let prefix = "validated export failed:";
|
||||||
|
match &ve[0] {
|
||||||
|
ValidationError::TooLongString => {
|
||||||
|
bail!(Span::detached(), "{prefix} a PDF string longer than 32767 characters";
|
||||||
|
hint: "make sure title and author names are short enough");
|
||||||
|
}
|
||||||
|
// Should in theory never occur, as krilla always trims font names
|
||||||
|
ValidationError::TooLongName => {
|
||||||
|
bail!(Span::detached(), "{prefix} a PDF name longer than 127 characters";
|
||||||
|
hint: "perhaps a font name is too long");
|
||||||
|
}
|
||||||
|
ValidationError::TooLongArray => {
|
||||||
|
bail!(Span::detached(), "{prefix} a PDF array longer than 8191 elements";
|
||||||
|
hint: "this can happen if you have a very long text in a single line");
|
||||||
|
}
|
||||||
|
ValidationError::TooLongDictionary => {
|
||||||
|
bail!(Span::detached(), "{prefix} a PDF dictionary had more than 4095 entries";
|
||||||
|
hint: "try reducing the complexity of your document");
|
||||||
|
}
|
||||||
|
ValidationError::TooLargeFloat => {
|
||||||
|
bail!(Span::detached(), "{prefix} a PDF float was larger than the allowed limit";
|
||||||
|
hint: "try exporting using a higher PDF version");
|
||||||
|
}
|
||||||
|
ValidationError::TooManyIndirectObjects => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF has too many indirect objects";
|
||||||
|
hint: "reduce the size of your document");
|
||||||
|
}
|
||||||
|
ValidationError::TooHighQNestingLevel => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF has too high q nesting";
|
||||||
|
hint: "reduce the number of nested containers");
|
||||||
|
}
|
||||||
|
ValidationError::ContainsPostScript => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF contains PostScript code";
|
||||||
|
hint: "sweep gradients are not supported in this PDF standard");
|
||||||
|
}
|
||||||
|
ValidationError::MissingCMYKProfile => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF is missing a CMYK profile";
|
||||||
|
hint: "CMYK colors are not yet supported in this export mode");
|
||||||
|
}
|
||||||
|
ValidationError::ContainsNotDefGlyph => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF contains the .notdef glyph";
|
||||||
|
hint: "ensure all text can be displayed using a font");
|
||||||
|
}
|
||||||
|
ValidationError::InvalidCodepointMapping(_, _) => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF contains the disallowed codepoints";
|
||||||
|
hint: "make sure you don't use the Unicode characters 0x0, 0xFEFF or 0xFFFE");
|
||||||
|
}
|
||||||
|
ValidationError::UnicodePrivateArea(_, _) => {
|
||||||
|
bail!(Span::detached(), "{prefix} the PDF contains characters from the Unicode private area";
|
||||||
|
hint: "remove the text containing codepoints from the Unicode private area");
|
||||||
|
}
|
||||||
|
ValidationError::Transparency => {
|
||||||
|
bail!(Span::detached(), "{prefix} document contains transparency";
|
||||||
|
hint: "remove any transparency in your document and your SVGs";
|
||||||
|
hint: "export using a different standard that supports transparency"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// The below errors cannot occur yet, only once Typst supports PDF/A and PDF/UA.
|
||||||
|
ValidationError::MissingAnnotationAltText => {
|
||||||
|
bail!(Span::detached(), "{prefix} missing annotation alt text";
|
||||||
|
hint: "please report this as a bug");
|
||||||
|
}
|
||||||
|
ValidationError::MissingAltText => {
|
||||||
|
bail!(Span::detached(), "{prefix} missing alt text";
|
||||||
|
hint: "make sure your images and formulas have alt text");
|
||||||
|
}
|
||||||
|
ValidationError::NoDocumentLanguage => {
|
||||||
|
bail!(Span::detached(), "{prefix} missing document language";
|
||||||
|
hint: "set the language of the document");
|
||||||
|
}
|
||||||
|
// Needs to be set by Typst.
|
||||||
|
ValidationError::MissingHeadingTitle => {
|
||||||
|
bail!(Span::detached(), "{prefix} missing heading title";
|
||||||
|
hint: "please report this as a bug");
|
||||||
|
}
|
||||||
|
// Needs to be set by Typst.
|
||||||
|
ValidationError::MissingDocumentOutline => {
|
||||||
|
bail!(Span::detached(), "{prefix} missing document outline";
|
||||||
|
hint: "please report this as a bug");
|
||||||
|
}
|
||||||
|
ValidationError::NoDocumentTitle => {
|
||||||
|
bail!(Span::detached(), "{prefix} missing document title";
|
||||||
|
hint: "set the title of the document");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KrillaError::ImageError(i) => {
|
||||||
|
let span = gc.image_spans.get(&i).unwrap();
|
||||||
|
bail!(*span, "failed to process image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn krilla_date(datetime: Datetime, tz: bool) -> Option<krilla::metadata::DateTime> {
|
fn krilla_date(datetime: Datetime, tz: bool) -> Option<krilla::metadata::DateTime> {
|
||||||
@ -442,15 +550,24 @@ pub fn handle_text(
|
|||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
gc: &mut GlobalContext,
|
gc: &mut GlobalContext,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let font = gc
|
let typst_font = t.font.clone();
|
||||||
.fonts
|
|
||||||
.entry(t.font.clone())
|
let krilla_font = if let Some(font) = gc.fonts_forward.get(&typst_font) {
|
||||||
.or_insert_with(|| {
|
font.clone()
|
||||||
krilla::font::Font::new(Arc::new(t.font.data().clone()), t.font.index(), true)
|
} else {
|
||||||
// TODO: DOn't unwrap
|
let font = match krilla::font::Font::new(Arc::new(typst_font.data().clone()), typst_font.index(), true) {
|
||||||
.unwrap()
|
None => {
|
||||||
})
|
let font_str = font_to_str(&typst_font);
|
||||||
.clone();
|
bail!(Span::detached(), "failed to process font {font_str}");
|
||||||
|
}
|
||||||
|
Some(f) => f
|
||||||
|
};
|
||||||
|
|
||||||
|
gc.fonts_forward.insert(typst_font.clone(), font.clone());
|
||||||
|
gc.fonts_backward.insert(font.clone(), typst_font.clone());
|
||||||
|
|
||||||
|
font
|
||||||
|
};
|
||||||
|
|
||||||
*gc.languages.entry(t.lang).or_insert(0) += t.glyphs.len();
|
*gc.languages.entry(t.lang).or_insert(0) += t.glyphs.len();
|
||||||
|
|
||||||
@ -473,7 +590,7 @@ pub fn handle_text(
|
|||||||
krilla::geom::Point::from_xy(0.0, 0.0),
|
krilla::geom::Point::from_xy(0.0, 0.0),
|
||||||
fill,
|
fill,
|
||||||
&glyphs,
|
&glyphs,
|
||||||
font.clone(),
|
krilla_font.clone(),
|
||||||
text,
|
text,
|
||||||
size.to_f32(),
|
size.to_f32(),
|
||||||
GlyphUnits::Normalized,
|
GlyphUnits::Normalized,
|
||||||
@ -491,7 +608,7 @@ pub fn handle_text(
|
|||||||
krilla::geom::Point::from_xy(0.0, 0.0),
|
krilla::geom::Point::from_xy(0.0, 0.0),
|
||||||
stroke,
|
stroke,
|
||||||
&glyphs,
|
&glyphs,
|
||||||
font.clone(),
|
krilla_font.clone(),
|
||||||
text,
|
text,
|
||||||
size.to_f32(),
|
size.to_f32(),
|
||||||
GlyphUnits::Normalized,
|
GlyphUnits::Normalized,
|
||||||
|
@ -4,14 +4,11 @@
|
|||||||
mod image;
|
mod image;
|
||||||
mod krilla;
|
mod krilla;
|
||||||
mod paint;
|
mod paint;
|
||||||
mod primitive;
|
mod util;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use typst_library::diag::{SourceResult};
|
||||||
use std::hash::Hash;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use typst_library::diag::{bail, SourceResult, StrResult};
|
|
||||||
use typst_library::foundations::{Datetime, Smart};
|
use typst_library::foundations::{Datetime, Smart};
|
||||||
use typst_library::layout::{Abs, Em, PageRanges, PagedDocument, Transform};
|
use typst_library::layout::{PageRanges, PagedDocument};
|
||||||
|
|
||||||
/// Export a document into a PDF file.
|
/// Export a document into a PDF file.
|
||||||
///
|
///
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
//! Convert paint types from typst to krilla.
|
//! Convert paint types from typst to krilla.
|
||||||
|
|
||||||
use crate::krilla::{process_frame, FrameContext, GlobalContext, Transforms};
|
use crate::krilla::{process_frame, FrameContext, GlobalContext, Transforms};
|
||||||
use crate::primitive::{AbsExt, FillRuleExt, LineCapExt, LineJoinExt, TransformExt};
|
use crate::util::{AbsExt, FillRuleExt, LineCapExt, LineJoinExt, TransformExt};
|
||||||
use krilla::geom::NormalizedF32;
|
use krilla::geom::NormalizedF32;
|
||||||
use krilla::page::{NumberingStyle, PageLabel};
|
|
||||||
use krilla::paint::SpreadMethod;
|
use krilla::paint::SpreadMethod;
|
||||||
use krilla::surface::Surface;
|
use krilla::surface::Surface;
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::layout::{Abs, Angle, Quadrant, Ratio, Transform};
|
use typst_library::layout::{Abs, Angle, Quadrant, Ratio, Transform};
|
||||||
use typst_library::model::Numbering;
|
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
Color, ColorSpace, DashPattern, FillRule, FixedStroke, Gradient, Paint, Pattern,
|
Color, ColorSpace, DashPattern, FillRule, FixedStroke, Gradient, Paint, Pattern,
|
||||||
RatioOrAngle, RelativeTo, WeightedColor,
|
RatioOrAngle, RelativeTo, WeightedColor,
|
||||||
@ -86,61 +83,6 @@ fn paint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait PageLabelExt {
|
|
||||||
fn generate(numbering: &Numbering, number: usize) -> Option<PageLabel>;
|
|
||||||
fn arabic(number: usize) -> PageLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PageLabelExt for PageLabel {
|
|
||||||
/// Create a new `PageLabel` from a `Numbering` applied to a page
|
|
||||||
/// number.
|
|
||||||
fn generate(numbering: &Numbering, number: usize) -> Option<PageLabel> {
|
|
||||||
{
|
|
||||||
let Numbering::Pattern(pat) = numbering else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let (prefix, kind) = pat.pieces.first()?;
|
|
||||||
|
|
||||||
// If there is a suffix, we cannot use the common style optimisation,
|
|
||||||
// since PDF does not provide a suffix field.
|
|
||||||
let style = if pat.suffix.is_empty() {
|
|
||||||
use krilla::page::NumberingStyle as Style;
|
|
||||||
use typst_library::model::NumberingKind as Kind;
|
|
||||||
match kind {
|
|
||||||
Kind::Arabic => Some(Style::Arabic),
|
|
||||||
Kind::LowerRoman => Some(Style::LowerRoman),
|
|
||||||
Kind::UpperRoman => Some(Style::UpperRoman),
|
|
||||||
Kind::LowerLatin if number <= 26 => Some(Style::LowerAlpha),
|
|
||||||
Kind::LowerLatin if number <= 26 => Some(Style::UpperAlpha),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prefix and offset depend on the style: If it is supported by the PDF
|
|
||||||
// spec, we use the given prefix and an offset. Otherwise, everything
|
|
||||||
// goes into prefix.
|
|
||||||
let prefix = if style.is_none() {
|
|
||||||
Some(pat.apply(&[number]))
|
|
||||||
} else {
|
|
||||||
(!prefix.is_empty()).then(|| prefix.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let offset = style.and(NonZeroUsize::new(number));
|
|
||||||
Some(PageLabel::new(style, prefix.map(|s| s.to_string()), offset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an arabic page label with the specified page number.
|
|
||||||
/// For example, this will display page label `11` when given the page
|
|
||||||
/// number 11.
|
|
||||||
fn arabic(number: usize) -> PageLabel {
|
|
||||||
PageLabel::new(Some(NumberingStyle::Arabic), None, NonZeroUsize::new(number))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn convert_pattern(
|
pub(crate) fn convert_pattern(
|
||||||
gc: &mut GlobalContext,
|
gc: &mut GlobalContext,
|
||||||
pattern: &Pattern,
|
pattern: &Pattern,
|
||||||
@ -293,7 +235,6 @@ fn convert_gradient(
|
|||||||
// If we have a hue index or are using Oklab, we will create several
|
// If we have a hue index or are using Oklab, we will create several
|
||||||
// stops in-between to make the gradient smoother without interpolation
|
// stops in-between to make the gradient smoother without interpolation
|
||||||
// issues with native color spaces.
|
// issues with native color spaces.
|
||||||
let mut last_c = first.0;
|
|
||||||
if gradient.space().hue_index().is_some() {
|
if gradient.space().hue_index().is_some() {
|
||||||
for i in 0..=32 {
|
for i in 0..=32 {
|
||||||
let t = i as f64 / 32.0;
|
let t = i as f64 / 32.0;
|
||||||
@ -302,7 +243,6 @@ fn convert_gradient(
|
|||||||
|
|
||||||
let c = gradient.sample(RatioOrAngle::Ratio(real_t));
|
let c = gradient.sample(RatioOrAngle::Ratio(real_t));
|
||||||
add_single(&c, real_t);
|
add_single(&c, real_t);
|
||||||
last_c = c;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
//! Convert basic primitive types from typst to krilla.
|
|
||||||
|
|
||||||
use typst_library::layout::{Abs, Point, Size, Transform};
|
|
||||||
use typst_library::visualize::{FillRule, LineCap, LineJoin};
|
|
||||||
|
|
||||||
|
|
||||||
pub(crate) trait SizeExt {
|
|
||||||
fn as_krilla(&self) -> krilla::geom::Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizeExt for Size {
|
|
||||||
fn as_krilla(&self) -> krilla::geom::Size {
|
|
||||||
krilla::geom::Size::from_wh(self.x.to_f32(), self.y.to_f32()).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait PointExt {
|
|
||||||
fn as_krilla(&self) -> krilla::geom::Point;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PointExt for Point {
|
|
||||||
fn as_krilla(&self) -> krilla::geom::Point {
|
|
||||||
krilla::geom::Point::from_xy(self.x.to_f32(), self.y.to_f32())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait LineCapExt {
|
|
||||||
fn as_krilla(&self) -> krilla::path::LineCap;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineCapExt for LineCap {
|
|
||||||
fn as_krilla(&self) -> krilla::path::LineCap {
|
|
||||||
match self {
|
|
||||||
LineCap::Butt => krilla::path::LineCap::Butt,
|
|
||||||
LineCap::Round => krilla::path::LineCap::Round,
|
|
||||||
LineCap::Square => krilla::path::LineCap::Square,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait LineJoinExt {
|
|
||||||
fn as_krilla(&self) -> krilla::path::LineJoin;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineJoinExt for LineJoin {
|
|
||||||
fn as_krilla(&self) -> krilla::path::LineJoin {
|
|
||||||
match self {
|
|
||||||
LineJoin::Miter => krilla::path::LineJoin::Miter,
|
|
||||||
LineJoin::Round => krilla::path::LineJoin::Round,
|
|
||||||
LineJoin::Bevel => krilla::path::LineJoin::Bevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait TransformExt {
|
|
||||||
fn as_krilla(&self) -> krilla::geom::Transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransformExt for Transform {
|
|
||||||
fn as_krilla(&self) -> krilla::geom::Transform {
|
|
||||||
krilla::geom::Transform::from_row(
|
|
||||||
self.sx.get() as f32,
|
|
||||||
self.ky.get() as f32,
|
|
||||||
self.kx.get() as f32,
|
|
||||||
self.sy.get() as f32,
|
|
||||||
self.tx.to_f32(),
|
|
||||||
self.ty.to_f32(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait FillRuleExt {
|
|
||||||
fn as_krilla(&self) -> krilla::path::FillRule;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FillRuleExt for FillRule {
|
|
||||||
fn as_krilla(&self) -> krilla::path::FillRule {
|
|
||||||
match self {
|
|
||||||
FillRule::NonZero => krilla::path::FillRule::NonZero,
|
|
||||||
FillRule::EvenOdd => krilla::path::FillRule::EvenOdd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods for [`Abs`].
|
|
||||||
pub(crate) trait AbsExt {
|
|
||||||
/// Convert an to a number of points.
|
|
||||||
fn to_f32(self) -> f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AbsExt for Abs {
|
|
||||||
fn to_f32(self) -> f32 {
|
|
||||||
self.to_pt() as f32
|
|
||||||
}
|
|
||||||
}
|
|
160
crates/typst-pdf/src/util.rs
Normal file
160
crates/typst-pdf/src/util.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
//! Convert basic primitive types from typst to krilla.
|
||||||
|
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
use krilla::page::{NumberingStyle, PageLabel};
|
||||||
|
use typst_library::layout::{Abs, Point, Size, Transform};
|
||||||
|
use typst_library::model::Numbering;
|
||||||
|
use typst_library::text::Font;
|
||||||
|
use typst_library::visualize::{FillRule, LineCap, LineJoin};
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) trait SizeExt {
|
||||||
|
fn as_krilla(&self) -> krilla::geom::Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SizeExt for Size {
|
||||||
|
fn as_krilla(&self) -> krilla::geom::Size {
|
||||||
|
krilla::geom::Size::from_wh(self.x.to_f32(), self.y.to_f32()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait PointExt {
|
||||||
|
fn as_krilla(&self) -> krilla::geom::Point;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointExt for Point {
|
||||||
|
fn as_krilla(&self) -> krilla::geom::Point {
|
||||||
|
krilla::geom::Point::from_xy(self.x.to_f32(), self.y.to_f32())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait LineCapExt {
|
||||||
|
fn as_krilla(&self) -> krilla::path::LineCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineCapExt for LineCap {
|
||||||
|
fn as_krilla(&self) -> krilla::path::LineCap {
|
||||||
|
match self {
|
||||||
|
LineCap::Butt => krilla::path::LineCap::Butt,
|
||||||
|
LineCap::Round => krilla::path::LineCap::Round,
|
||||||
|
LineCap::Square => krilla::path::LineCap::Square,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait LineJoinExt {
|
||||||
|
fn as_krilla(&self) -> krilla::path::LineJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineJoinExt for LineJoin {
|
||||||
|
fn as_krilla(&self) -> krilla::path::LineJoin {
|
||||||
|
match self {
|
||||||
|
LineJoin::Miter => krilla::path::LineJoin::Miter,
|
||||||
|
LineJoin::Round => krilla::path::LineJoin::Round,
|
||||||
|
LineJoin::Bevel => krilla::path::LineJoin::Bevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait TransformExt {
|
||||||
|
fn as_krilla(&self) -> krilla::geom::Transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransformExt for Transform {
|
||||||
|
fn as_krilla(&self) -> krilla::geom::Transform {
|
||||||
|
krilla::geom::Transform::from_row(
|
||||||
|
self.sx.get() as f32,
|
||||||
|
self.ky.get() as f32,
|
||||||
|
self.kx.get() as f32,
|
||||||
|
self.sy.get() as f32,
|
||||||
|
self.tx.to_f32(),
|
||||||
|
self.ty.to_f32(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait FillRuleExt {
|
||||||
|
fn as_krilla(&self) -> krilla::path::FillRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FillRuleExt for FillRule {
|
||||||
|
fn as_krilla(&self) -> krilla::path::FillRule {
|
||||||
|
match self {
|
||||||
|
FillRule::NonZero => krilla::path::FillRule::NonZero,
|
||||||
|
FillRule::EvenOdd => krilla::path::FillRule::EvenOdd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional methods for [`Abs`].
|
||||||
|
pub(crate) trait AbsExt {
|
||||||
|
/// Convert an to a number of points.
|
||||||
|
fn to_f32(self) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbsExt for Abs {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self.to_pt() as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait PageLabelExt {
|
||||||
|
fn generate(numbering: &Numbering, number: usize) -> Option<PageLabel>;
|
||||||
|
fn arabic(number: usize) -> PageLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageLabelExt for PageLabel {
|
||||||
|
/// Create a new `PageLabel` from a `Numbering` applied to a page
|
||||||
|
/// number.
|
||||||
|
fn generate(numbering: &Numbering, number: usize) -> Option<PageLabel> {
|
||||||
|
{
|
||||||
|
let Numbering::Pattern(pat) = numbering else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (prefix, kind) = pat.pieces.first()?;
|
||||||
|
|
||||||
|
// If there is a suffix, we cannot use the common style optimisation,
|
||||||
|
// since PDF does not provide a suffix field.
|
||||||
|
let style = if pat.suffix.is_empty() {
|
||||||
|
use krilla::page::NumberingStyle as Style;
|
||||||
|
use typst_library::model::NumberingKind as Kind;
|
||||||
|
match kind {
|
||||||
|
Kind::Arabic => Some(Style::Arabic),
|
||||||
|
Kind::LowerRoman => Some(Style::LowerRoman),
|
||||||
|
Kind::UpperRoman => Some(Style::UpperRoman),
|
||||||
|
Kind::LowerLatin if number <= 26 => Some(Style::LowerAlpha),
|
||||||
|
Kind::LowerLatin if number <= 26 => Some(Style::UpperAlpha),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prefix and offset depend on the style: If it is supported by the PDF
|
||||||
|
// spec, we use the given prefix and an offset. Otherwise, everything
|
||||||
|
// goes into prefix.
|
||||||
|
let prefix = if style.is_none() {
|
||||||
|
Some(pat.apply(&[number]))
|
||||||
|
} else {
|
||||||
|
(!prefix.is_empty()).then(|| prefix.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = style.and(NonZeroUsize::new(number));
|
||||||
|
Some(PageLabel::new(style, prefix.map(|s| s.to_string()), offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an arabic page label with the specified page number.
|
||||||
|
/// For example, this will display page label `11` when given the page
|
||||||
|
/// number 11.
|
||||||
|
fn arabic(number: usize) -> PageLabel {
|
||||||
|
PageLabel::new(Some(NumberingStyle::Arabic), None, NonZeroUsize::new(number))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn font_to_str(font: &Font) -> String {
|
||||||
|
let font_family = &font.info().family;
|
||||||
|
let font_variant = font.info().variant;
|
||||||
|
format!("{} ({:?})", font_family, font_variant)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user