mirror of
https://github.com/typst/typst
synced 2025-08-18 17:08:33 +08:00
Compare commits
2 Commits
429f4b8792
...
dc49c869b8
Author | SHA1 | Date | |
---|---|---|---|
|
dc49c869b8 | ||
|
c86f9b3a2d |
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -413,7 +413,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "codex"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
|
||||
source = "git+https://github.com/typst/codex?rev=a5428cb#a5428cb9c81a41354d44b44dbd5a16a710bbd928"
|
||||
|
||||
[[package]]
|
||||
name = "color-print"
|
||||
@ -2861,7 +2861,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typst-assets"
|
||||
version = "0.13.1"
|
||||
source = "git+https://github.com/typst/typst-assets?rev=edf0d64#edf0d648376e29738a05a933af9ea99bb81557b1"
|
||||
source = "git+https://github.com/typst/typst-assets?rev=c1089b4#c1089b46c461bdde579c55caa941a3cc7dec3e8a"
|
||||
|
||||
[[package]]
|
||||
name = "typst-cli"
|
||||
@ -3032,7 +3032,6 @@ version = "0.13.1"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bumpalo",
|
||||
"codex",
|
||||
"comemo",
|
||||
"ecow",
|
||||
"hypher",
|
||||
|
@ -32,7 +32,7 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" }
|
||||
typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
|
||||
typst-timing = { path = "crates/typst-timing", version = "0.13.1" }
|
||||
typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
|
||||
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "edf0d64" }
|
||||
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "c1089b4" }
|
||||
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" }
|
||||
arrayvec = "0.7.4"
|
||||
az = "1.2"
|
||||
@ -47,7 +47,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete = "4.2.1"
|
||||
clap_mangen = "0.2.10"
|
||||
codespan-reporting = "0.11"
|
||||
codex = { git = "https://github.com/typst/codex", rev = "9ac86f9" }
|
||||
codex = { git = "https://github.com/typst/codex", rev = "a5428cb" }
|
||||
color-print = "0.3.6"
|
||||
comemo = "0.4"
|
||||
csv = "1"
|
||||
|
@ -1,72 +1,11 @@
|
||||
//! Conversion from Typst data types into CSS data types.
|
||||
|
||||
use std::fmt::{self, Display, Write};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use ecow::EcoString;
|
||||
use typst_library::html::{attr, HtmlElem};
|
||||
use typst_library::layout::{Length, Rel};
|
||||
use typst_library::layout::Length;
|
||||
use typst_library::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
|
||||
use typst_utils::Numeric;
|
||||
|
||||
/// Additional methods for [`HtmlElem`].
|
||||
pub trait HtmlElemExt {
|
||||
/// Adds the styles to an element if the property list is non-empty.
|
||||
fn with_styles(self, properties: Properties) -> Self;
|
||||
}
|
||||
|
||||
impl HtmlElemExt for HtmlElem {
|
||||
/// Adds CSS styles to an element.
|
||||
fn with_styles(self, properties: Properties) -> Self {
|
||||
if let Some(value) = properties.into_inline_styles() {
|
||||
self.with_attr(attr::style, value)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of CSS properties with values.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Properties(EcoString);
|
||||
|
||||
impl Properties {
|
||||
/// Creates an empty list.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Adds a new property to the list.
|
||||
pub fn push(&mut self, property: &str, value: impl Display) {
|
||||
if !self.0.is_empty() {
|
||||
self.0.push_str("; ");
|
||||
}
|
||||
write!(&mut self.0, "{property}: {value}").unwrap();
|
||||
}
|
||||
|
||||
/// Adds a new property in builder-style.
|
||||
#[expect(unused)]
|
||||
pub fn with(mut self, property: &str, value: impl Display) -> Self {
|
||||
self.push(property, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Turns this into a string suitable for use as an inline `style`
|
||||
/// attribute.
|
||||
pub fn into_inline_styles(self) -> Option<EcoString> {
|
||||
(!self.0.is_empty()).then_some(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rel(rel: Rel) -> impl Display {
|
||||
typst_utils::display(move |f| match (rel.abs.is_zero(), rel.rel.is_zero()) {
|
||||
(false, false) => {
|
||||
write!(f, "calc({}% + {})", rel.rel.get(), length(rel.abs))
|
||||
}
|
||||
(true, false) => write!(f, "{}%", rel.rel.get()),
|
||||
(_, true) => write!(f, "{}", length(rel.abs)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn length(length: Length) -> impl Display {
|
||||
typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
|
||||
(false, false) => {
|
||||
|
@ -3,12 +3,12 @@ use std::num::NonZeroUsize;
|
||||
use ecow::{eco_format, EcoVec};
|
||||
use typst_library::diag::warning;
|
||||
use typst_library::foundations::{
|
||||
Content, NativeElement, NativeRuleMap, ShowFn, Smart, StyleChain, Target,
|
||||
Content, NativeElement, NativeRuleMap, ShowFn, StyleChain, Target,
|
||||
};
|
||||
use typst_library::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag};
|
||||
use typst_library::introspection::{Counter, Locator};
|
||||
use typst_library::layout::resolve::{table_to_cellgrid, Cell, CellGrid, Entry};
|
||||
use typst_library::layout::{OuterVAlignment, Sizing};
|
||||
use typst_library::layout::OuterVAlignment;
|
||||
use typst_library::model::{
|
||||
Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption,
|
||||
FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem,
|
||||
@ -18,9 +18,6 @@ use typst_library::text::{
|
||||
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SpaceElem, StrikeElem,
|
||||
SubElem, SuperElem, UnderlineElem,
|
||||
};
|
||||
use typst_library::visualize::ImageElem;
|
||||
|
||||
use crate::css::{self, HtmlElemExt};
|
||||
|
||||
/// Register show rules for the [HTML target](Target::Html).
|
||||
pub fn register(rules: &mut NativeRuleMap) {
|
||||
@ -50,9 +47,6 @@ pub fn register(rules: &mut NativeRuleMap) {
|
||||
rules.register(Html, HIGHLIGHT_RULE);
|
||||
rules.register(Html, RAW_RULE);
|
||||
rules.register(Html, RAW_LINE_RULE);
|
||||
|
||||
// Visualize.
|
||||
rules.register(Html, IMAGE_RULE);
|
||||
}
|
||||
|
||||
const STRONG_RULE: ShowFn<StrongElem> = |elem, _, _| {
|
||||
@ -344,7 +338,7 @@ fn show_cellgrid(grid: CellGrid, styles: StyleChain) -> Content {
|
||||
fn show_cell(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
|
||||
let cell = cell.body.clone();
|
||||
let Some(cell) = cell.to_packed::<TableCell>() else { return cell };
|
||||
let mut attrs = HtmlAttrs::new();
|
||||
let mut attrs = HtmlAttrs::default();
|
||||
let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
|
||||
if let Some(colspan) = span(cell.colspan.get(styles)) {
|
||||
attrs.push(attr::colspan, colspan);
|
||||
@ -415,36 +409,3 @@ const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
|
||||
};
|
||||
|
||||
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
||||
|
||||
const IMAGE_RULE: ShowFn<ImageElem> = |elem, engine, styles| {
|
||||
let image = elem.decode(engine, styles)?;
|
||||
|
||||
let mut attrs = HtmlAttrs::new();
|
||||
attrs.push(attr::src, typst_svg::convert_image_to_base64_url(&image));
|
||||
|
||||
if let Some(alt) = elem.alt.get_cloned(styles) {
|
||||
attrs.push(attr::alt, alt);
|
||||
}
|
||||
|
||||
let mut inline = css::Properties::new();
|
||||
|
||||
// TODO: Exclude in semantic profile.
|
||||
if let Some(value) = typst_svg::convert_image_scaling(image.scaling()) {
|
||||
inline.push("image-rendering", value);
|
||||
}
|
||||
|
||||
// TODO: Exclude in semantic profile?
|
||||
match elem.width.get(styles) {
|
||||
Smart::Auto => {}
|
||||
Smart::Custom(rel) => inline.push("width", css::rel(rel)),
|
||||
}
|
||||
|
||||
// TODO: Exclude in semantic profile?
|
||||
match elem.height.get(styles) {
|
||||
Sizing::Auto => {}
|
||||
Sizing::Rel(rel) => inline.push("height", css::rel(rel)),
|
||||
Sizing::Fr(_) => {}
|
||||
}
|
||||
|
||||
Ok(HtmlElem::new(tag::img).with_attrs(attrs).with_styles(inline).pack())
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ use comemo::Track;
|
||||
use ecow::{eco_vec, EcoString, EcoVec};
|
||||
use typst::foundations::{Label, Styles, Value};
|
||||
use typst::layout::PagedDocument;
|
||||
use typst::model::{BibliographyElem, FigureElem};
|
||||
use typst::model::BibliographyElem;
|
||||
use typst::syntax::{ast, LinkedNode, SyntaxKind};
|
||||
|
||||
use crate::IdeWorld;
|
||||
@ -75,13 +75,8 @@ pub fn analyze_labels(
|
||||
for elem in document.introspector.all() {
|
||||
let Some(label) = elem.label() else { continue };
|
||||
let details = elem
|
||||
.to_packed::<FigureElem>()
|
||||
.and_then(|figure| match figure.caption.as_option() {
|
||||
Some(Some(caption)) => Some(caption.pack_ref()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(elem)
|
||||
.get_by_name("body")
|
||||
.get_by_name("caption")
|
||||
.or_else(|_| elem.get_by_name("body"))
|
||||
.ok()
|
||||
.and_then(|field| match field {
|
||||
Value::Content(content) => Some(content),
|
||||
|
@ -378,9 +378,4 @@ mod tests {
|
||||
.with_source("other.typ", "#let f = (x) => 1");
|
||||
test(&world, -4, Side::After).must_be_code("(..) => ..");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tooltip_reference() {
|
||||
test("#figure(caption: [Hi])[]<f> @f", -1, Side::Before).must_be_text("Hi");
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ typst-timing = { workspace = true }
|
||||
typst-utils = { workspace = true }
|
||||
az = { workspace = true }
|
||||
bumpalo = { workspace = true }
|
||||
codex = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
ecow = { workspace = true }
|
||||
hypher = { workspace = true }
|
||||
|
@ -1,11 +1,18 @@
|
||||
use typst_library::diag::SourceResult;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use typst_library::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
|
||||
use typst_library::engine::Engine;
|
||||
use typst_library::foundations::{Packed, StyleChain};
|
||||
use typst_library::foundations::{Bytes, Derived, Packed, Smart, StyleChain};
|
||||
use typst_library::introspection::Locator;
|
||||
use typst_library::layout::{
|
||||
Abs, Axes, FixedAlignment, Frame, FrameItem, Point, Region, Size,
|
||||
};
|
||||
use typst_library::visualize::{Curve, Image, ImageElem, ImageFit};
|
||||
use typst_library::loading::DataSource;
|
||||
use typst_library::text::families;
|
||||
use typst_library::visualize::{
|
||||
Curve, ExchangeFormat, Image, ImageElem, ImageFit, ImageFormat, ImageKind,
|
||||
RasterImage, SvgImage, VectorFormat,
|
||||
};
|
||||
|
||||
/// Layout the image.
|
||||
#[typst_macros::time(span = elem.span())]
|
||||
@ -16,7 +23,53 @@ pub fn layout_image(
|
||||
styles: StyleChain,
|
||||
region: Region,
|
||||
) -> SourceResult<Frame> {
|
||||
let image = elem.decode(engine, styles)?;
|
||||
let span = elem.span();
|
||||
|
||||
// Take the format that was explicitly defined, or parse the extension,
|
||||
// or try to detect the format.
|
||||
let Derived { source, derived: loaded } = &elem.source;
|
||||
let format = match elem.format.get(styles) {
|
||||
Smart::Custom(v) => v,
|
||||
Smart::Auto => determine_format(source, &loaded.data).at(span)?,
|
||||
};
|
||||
|
||||
// Warn the user if the image contains a foreign object. Not perfect
|
||||
// because the svg could also be encoded, but that's an edge case.
|
||||
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
||||
let has_foreign_object =
|
||||
memchr::memmem::find(&loaded.data, b"<foreignObject").is_some();
|
||||
|
||||
if has_foreign_object {
|
||||
engine.sink.warn(warning!(
|
||||
span,
|
||||
"image contains foreign object";
|
||||
hint: "SVG images with foreign objects might render incorrectly in typst";
|
||||
hint: "see https://github.com/typst/typst/issues/1421 for more information"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the image itself.
|
||||
let kind = match format {
|
||||
ImageFormat::Raster(format) => ImageKind::Raster(
|
||||
RasterImage::new(
|
||||
loaded.data.clone(),
|
||||
format,
|
||||
elem.icc.get_ref(styles).as_ref().map(|icc| icc.derived.clone()),
|
||||
)
|
||||
.at(span)?,
|
||||
),
|
||||
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
||||
SvgImage::with_fonts(
|
||||
loaded.data.clone(),
|
||||
engine.world,
|
||||
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
||||
)
|
||||
.within(loaded)?,
|
||||
),
|
||||
};
|
||||
|
||||
let image = Image::new(kind, elem.alt.get_cloned(styles), elem.scaling.get(styles));
|
||||
|
||||
// Determine the image's pixel aspect ratio.
|
||||
let pxw = image.width();
|
||||
@ -69,7 +122,7 @@ pub fn layout_image(
|
||||
// the frame to the target size, center aligning the image in the
|
||||
// process.
|
||||
let mut frame = Frame::soft(fitted);
|
||||
frame.push(Point::zero(), FrameItem::Image(image, fitted, elem.span()));
|
||||
frame.push(Point::zero(), FrameItem::Image(image, fitted, span));
|
||||
frame.resize(target, Axes::splat(FixedAlignment::Center));
|
||||
|
||||
// Create a clipping group if only part of the image should be visible.
|
||||
@ -79,3 +132,25 @@ pub fn layout_image(
|
||||
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
/// Try to determine the image format based on the data.
|
||||
fn determine_format(source: &DataSource, data: &Bytes) -> StrResult<ImageFormat> {
|
||||
if let DataSource::Path(path) = source {
|
||||
let ext = std::path::Path::new(path.as_str())
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
match ext.as_str() {
|
||||
"png" => return Ok(ExchangeFormat::Png.into()),
|
||||
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
|
||||
"gif" => return Ok(ExchangeFormat::Gif.into()),
|
||||
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
|
||||
"webp" => return Ok(ExchangeFormat::Webp.into()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImageFormat::detect(data).ok_or("unknown image format")?)
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use std::f64::consts::SQRT_2;
|
||||
|
||||
use codex::styling::{to_style, MathStyle};
|
||||
use ecow::EcoString;
|
||||
use typst_library::diag::SourceResult;
|
||||
use typst_library::foundations::{Packed, StyleChain, SymbolElem};
|
||||
use typst_library::layout::{Abs, Size};
|
||||
use typst_library::math::{EquationElem, MathSize};
|
||||
use typst_library::math::{EquationElem, MathSize, MathVariant};
|
||||
use typst_library::text::{
|
||||
BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric,
|
||||
};
|
||||
@ -65,21 +64,12 @@ fn layout_inline_text(
|
||||
ctx: &mut MathContext,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<FrameFragment> {
|
||||
let variant = styles.get(EquationElem::variant);
|
||||
let bold = styles.get(EquationElem::bold);
|
||||
// Disable auto-italic.
|
||||
let italic = styles.get(EquationElem::italic).or(Some(false));
|
||||
|
||||
if text.chars().all(|c| c.is_ascii_digit() || c == '.') {
|
||||
// Small optimization for numbers. Note that this lays out slightly
|
||||
// differently to normal text and is worth re-evaluating in the future.
|
||||
let mut fragments = vec![];
|
||||
for unstyled_c in text.chars() {
|
||||
// This is fine as ascii digits and '.' can never end up as more
|
||||
// than a single char after styling.
|
||||
let style = MathStyle::select(unstyled_c, variant, bold, italic);
|
||||
let c = to_style(unstyled_c, style).next().unwrap();
|
||||
|
||||
let c = styled_char(styles, unstyled_c, false);
|
||||
let glyph = GlyphFragment::new_char(ctx.font, styles, c, span)?;
|
||||
fragments.push(glyph.into());
|
||||
}
|
||||
@ -93,10 +83,8 @@ fn layout_inline_text(
|
||||
.map(|p| p.wrap());
|
||||
|
||||
let styles = styles.chain(&local);
|
||||
let styled_text: EcoString = text
|
||||
.chars()
|
||||
.flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
|
||||
.collect();
|
||||
let styled_text: EcoString =
|
||||
text.chars().map(|c| styled_char(styles, c, false)).collect();
|
||||
|
||||
let spaced = styled_text.graphemes(true).nth(1).is_some();
|
||||
let elem = TextElem::packed(styled_text).spanned(span);
|
||||
@ -136,16 +124,9 @@ pub fn layout_symbol(
|
||||
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
|
||||
_ => (elem.text, styles),
|
||||
};
|
||||
|
||||
let variant = styles.get(EquationElem::variant);
|
||||
let bold = styles.get(EquationElem::bold);
|
||||
let italic = styles.get(EquationElem::italic);
|
||||
|
||||
let style = MathStyle::select(unstyled_c, variant, bold, italic);
|
||||
let text: EcoString = to_style(unstyled_c, style).collect();
|
||||
|
||||
let c = styled_char(styles, unstyled_c, true);
|
||||
let fragment: MathFragment =
|
||||
match GlyphFragment::new(ctx.font, symbol_styles, &text, elem.span()) {
|
||||
match GlyphFragment::new_char(ctx.font, symbol_styles, c, elem.span()) {
|
||||
Ok(mut glyph) => {
|
||||
adjust_glyph_layout(&mut glyph, ctx, styles);
|
||||
glyph.into()
|
||||
@ -153,7 +134,8 @@ pub fn layout_symbol(
|
||||
Err(_) => {
|
||||
// Not in the math font, fallback to normal inline text layout.
|
||||
// TODO: Should replace this with proper fallback in [`GlyphFragment::new`].
|
||||
layout_inline_text(&text, elem.span(), ctx, styles)?.into()
|
||||
layout_inline_text(c.encode_utf8(&mut [0; 4]), elem.span(), ctx, styles)?
|
||||
.into()
|
||||
}
|
||||
};
|
||||
ctx.push(fragment);
|
||||
@ -179,6 +161,226 @@ fn adjust_glyph_layout(
|
||||
}
|
||||
}
|
||||
|
||||
/// Style the character by selecting the unicode codepoint for italic, bold,
|
||||
/// caligraphic, etc.
|
||||
///
|
||||
/// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
|
||||
/// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
|
||||
fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char {
|
||||
use MathVariant::*;
|
||||
|
||||
let variant = styles.get(EquationElem::variant);
|
||||
let bold = styles.get(EquationElem::bold);
|
||||
let italic = styles.get(EquationElem::italic).unwrap_or(
|
||||
auto_italic
|
||||
&& matches!(
|
||||
c,
|
||||
'a'..='z' | 'ħ' | 'ı' | 'ȷ' | 'A'..='Z' |
|
||||
'α'..='ω' | '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
|
||||
)
|
||||
&& matches!(variant, Sans | Serif),
|
||||
);
|
||||
|
||||
if let Some(c) = basic_exception(c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
if let Some(c) = latin_exception(c, variant, bold, italic) {
|
||||
return c;
|
||||
}
|
||||
|
||||
if let Some(c) = greek_exception(c, variant, bold, italic) {
|
||||
return c;
|
||||
}
|
||||
|
||||
let base = match c {
|
||||
'A'..='Z' => 'A',
|
||||
'a'..='z' => 'a',
|
||||
'Α'..='Ω' => 'Α',
|
||||
'α'..='ω' => 'α',
|
||||
'0'..='9' => '0',
|
||||
// Hebrew Alef -> Dalet.
|
||||
'\u{05D0}'..='\u{05D3}' => '\u{05D0}',
|
||||
_ => return c,
|
||||
};
|
||||
|
||||
let tuple = (variant, bold, italic);
|
||||
let start = match c {
|
||||
// Latin upper.
|
||||
'A'..='Z' => match tuple {
|
||||
(Serif, false, false) => 0x0041,
|
||||
(Serif, true, false) => 0x1D400,
|
||||
(Serif, false, true) => 0x1D434,
|
||||
(Serif, true, true) => 0x1D468,
|
||||
(Sans, false, false) => 0x1D5A0,
|
||||
(Sans, true, false) => 0x1D5D4,
|
||||
(Sans, false, true) => 0x1D608,
|
||||
(Sans, true, true) => 0x1D63C,
|
||||
(Cal, false, _) => 0x1D49C,
|
||||
(Cal, true, _) => 0x1D4D0,
|
||||
(Frak, false, _) => 0x1D504,
|
||||
(Frak, true, _) => 0x1D56C,
|
||||
(Mono, _, _) => 0x1D670,
|
||||
(Bb, _, _) => 0x1D538,
|
||||
},
|
||||
|
||||
// Latin lower.
|
||||
'a'..='z' => match tuple {
|
||||
(Serif, false, false) => 0x0061,
|
||||
(Serif, true, false) => 0x1D41A,
|
||||
(Serif, false, true) => 0x1D44E,
|
||||
(Serif, true, true) => 0x1D482,
|
||||
(Sans, false, false) => 0x1D5BA,
|
||||
(Sans, true, false) => 0x1D5EE,
|
||||
(Sans, false, true) => 0x1D622,
|
||||
(Sans, true, true) => 0x1D656,
|
||||
(Cal, false, _) => 0x1D4B6,
|
||||
(Cal, true, _) => 0x1D4EA,
|
||||
(Frak, false, _) => 0x1D51E,
|
||||
(Frak, true, _) => 0x1D586,
|
||||
(Mono, _, _) => 0x1D68A,
|
||||
(Bb, _, _) => 0x1D552,
|
||||
},
|
||||
|
||||
// Greek upper.
|
||||
'Α'..='Ω' => match tuple {
|
||||
(Serif, false, false) => 0x0391,
|
||||
(Serif, true, false) => 0x1D6A8,
|
||||
(Serif, false, true) => 0x1D6E2,
|
||||
(Serif, true, true) => 0x1D71C,
|
||||
(Sans, _, false) => 0x1D756,
|
||||
(Sans, _, true) => 0x1D790,
|
||||
(Cal | Frak | Mono | Bb, _, _) => return c,
|
||||
},
|
||||
|
||||
// Greek lower.
|
||||
'α'..='ω' => match tuple {
|
||||
(Serif, false, false) => 0x03B1,
|
||||
(Serif, true, false) => 0x1D6C2,
|
||||
(Serif, false, true) => 0x1D6FC,
|
||||
(Serif, true, true) => 0x1D736,
|
||||
(Sans, _, false) => 0x1D770,
|
||||
(Sans, _, true) => 0x1D7AA,
|
||||
(Cal | Frak | Mono | Bb, _, _) => return c,
|
||||
},
|
||||
|
||||
// Hebrew Alef -> Dalet.
|
||||
'\u{05D0}'..='\u{05D3}' => 0x2135,
|
||||
|
||||
// Numbers.
|
||||
'0'..='9' => match tuple {
|
||||
(Serif, false, _) => 0x0030,
|
||||
(Serif, true, _) => 0x1D7CE,
|
||||
(Bb, _, _) => 0x1D7D8,
|
||||
(Sans, false, _) => 0x1D7E2,
|
||||
(Sans, true, _) => 0x1D7EC,
|
||||
(Mono, _, _) => 0x1D7F6,
|
||||
(Cal | Frak, _, _) => return c,
|
||||
},
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
std::char::from_u32(start + (c as u32 - base as u32)).unwrap()
|
||||
}
|
||||
|
||||
fn basic_exception(c: char) -> Option<char> {
|
||||
Some(match c {
|
||||
'〈' => '⟨',
|
||||
'〉' => '⟩',
|
||||
'《' => '⟪',
|
||||
'》' => '⟫',
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn latin_exception(
|
||||
c: char,
|
||||
variant: MathVariant,
|
||||
bold: bool,
|
||||
italic: bool,
|
||||
) -> Option<char> {
|
||||
use MathVariant::*;
|
||||
Some(match (c, variant, bold, italic) {
|
||||
('B', Cal, false, _) => 'ℬ',
|
||||
('E', Cal, false, _) => 'ℰ',
|
||||
('F', Cal, false, _) => 'ℱ',
|
||||
('H', Cal, false, _) => 'ℋ',
|
||||
('I', Cal, false, _) => 'ℐ',
|
||||
('L', Cal, false, _) => 'ℒ',
|
||||
('M', Cal, false, _) => 'ℳ',
|
||||
('R', Cal, false, _) => 'ℛ',
|
||||
('C', Frak, false, _) => 'ℭ',
|
||||
('H', Frak, false, _) => 'ℌ',
|
||||
('I', Frak, false, _) => 'ℑ',
|
||||
('R', Frak, false, _) => 'ℜ',
|
||||
('Z', Frak, false, _) => 'ℨ',
|
||||
('C', Bb, ..) => 'ℂ',
|
||||
('H', Bb, ..) => 'ℍ',
|
||||
('N', Bb, ..) => 'ℕ',
|
||||
('P', Bb, ..) => 'ℙ',
|
||||
('Q', Bb, ..) => 'ℚ',
|
||||
('R', Bb, ..) => 'ℝ',
|
||||
('Z', Bb, ..) => 'ℤ',
|
||||
('D', Bb, _, true) => 'ⅅ',
|
||||
('d', Bb, _, true) => 'ⅆ',
|
||||
('e', Bb, _, true) => 'ⅇ',
|
||||
('i', Bb, _, true) => 'ⅈ',
|
||||
('j', Bb, _, true) => 'ⅉ',
|
||||
('h', Serif, false, true) => 'ℎ',
|
||||
('e', Cal, false, _) => 'ℯ',
|
||||
('g', Cal, false, _) => 'ℊ',
|
||||
('o', Cal, false, _) => 'ℴ',
|
||||
('ħ', Serif, .., true) => 'ℏ',
|
||||
('ı', Serif, .., true) => '𝚤',
|
||||
('ȷ', Serif, .., true) => '𝚥',
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn greek_exception(
|
||||
c: char,
|
||||
variant: MathVariant,
|
||||
bold: bool,
|
||||
italic: bool,
|
||||
) -> Option<char> {
|
||||
use MathVariant::*;
|
||||
if c == 'Ϝ' && variant == Serif && bold {
|
||||
return Some('𝟊');
|
||||
}
|
||||
if c == 'ϝ' && variant == Serif && bold {
|
||||
return Some('𝟋');
|
||||
}
|
||||
|
||||
let list = match c {
|
||||
'ϴ' => ['𝚹', '𝛳', '𝜭', '𝝧', '𝞡', 'ϴ'],
|
||||
'∇' => ['𝛁', '𝛻', '𝜵', '𝝯', '𝞩', '∇'],
|
||||
'∂' => ['𝛛', '𝜕', '𝝏', '𝞉', '𝟃', '∂'],
|
||||
'ϵ' => ['𝛜', '𝜖', '𝝐', '𝞊', '𝟄', 'ϵ'],
|
||||
'ϑ' => ['𝛝', '𝜗', '𝝑', '𝞋', '𝟅', 'ϑ'],
|
||||
'ϰ' => ['𝛞', '𝜘', '𝝒', '𝞌', '𝟆', 'ϰ'],
|
||||
'ϕ' => ['𝛟', '𝜙', '𝝓', '𝞍', '𝟇', 'ϕ'],
|
||||
'ϱ' => ['𝛠', '𝜚', '𝝔', '𝞎', '𝟈', 'ϱ'],
|
||||
'ϖ' => ['𝛡', '𝜛', '𝝕', '𝞏', '𝟉', 'ϖ'],
|
||||
'Γ' => ['𝚪', '𝛤', '𝜞', '𝝘', '𝞒', 'ℾ'],
|
||||
'γ' => ['𝛄', '𝛾', '𝜸', '𝝲', '𝞬', 'ℽ'],
|
||||
'Π' => ['𝚷', '𝛱', '𝜫', '𝝥', '𝞟', 'ℿ'],
|
||||
'π' => ['𝛑', '𝜋', '𝝅', '𝝿', '𝞹', 'ℼ'],
|
||||
'∑' => ['∑', '∑', '∑', '∑', '∑', '⅀'],
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(match (variant, bold, italic) {
|
||||
(Serif, true, false) => list[0],
|
||||
(Serif, false, true) => list[1],
|
||||
(Serif, true, true) => list[2],
|
||||
(Sans, _, false) => list[3],
|
||||
(Sans, _, true) => list[4],
|
||||
(Bb, ..) => list[5],
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The non-dotless version of a dotless character that can be used with the
|
||||
/// `dtls` OpenType feature.
|
||||
pub fn try_dotless(c: char) -> Option<char> {
|
||||
|
@ -64,16 +64,6 @@ impl<T: NativeElement> Packed<T> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Pack back into a reference to content.
|
||||
pub fn pack_ref(&self) -> &Content {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Pack back into a mutable reference to content.
|
||||
pub fn pack_mut(&mut self) -> &mut Content {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
/// Extract the raw underlying element.
|
||||
pub fn unpack(self) -> T {
|
||||
// This function doesn't yet need owned self, but might in the future.
|
||||
@ -104,6 +94,10 @@ impl<T: NativeElement> Packed<T> {
|
||||
pub fn set_location(&mut self, location: Location) {
|
||||
self.0.set_location(location);
|
||||
}
|
||||
|
||||
pub fn as_content(&self) -> &Content {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NativeElement> AsRef<T> for Packed<T> {
|
||||
|
@ -141,7 +141,7 @@ impl RawContent {
|
||||
|
||||
/// Clones a packed element into new raw content.
|
||||
pub(super) fn clone_impl<E: NativeElement>(elem: &Packed<E>) -> Self {
|
||||
let raw = &elem.pack_ref().0;
|
||||
let raw = &elem.as_content().0;
|
||||
let header = raw.header();
|
||||
RawContent::create(
|
||||
elem.as_ref().clone(),
|
||||
|
@ -46,7 +46,7 @@ impl i64 {
|
||||
/// or smaller than the minimum 64-bit signed integer.
|
||||
///
|
||||
/// - Booleans are converted to `0` or `1`.
|
||||
/// - Floats and decimals are rounded to the next 64-bit integer towards zero.
|
||||
/// - Floats and decimals are truncated to the corresponding 64-bit integer.
|
||||
/// - Strings are parsed in base 10.
|
||||
///
|
||||
/// ```example
|
||||
|
@ -165,11 +165,6 @@ cast! {
|
||||
pub struct HtmlAttrs(pub EcoVec<(HtmlAttr, EcoString)>);
|
||||
|
||||
impl HtmlAttrs {
|
||||
/// Creates an empty attribute list.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Add an attribute.
|
||||
pub fn push(&mut self, attr: HtmlAttr, value: impl Into<EcoString>) {
|
||||
self.0.push((attr, value.into()));
|
||||
|
@ -1,6 +1,5 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use codex::styling::MathVariant;
|
||||
use typst_utils::NonZeroExt;
|
||||
use unicode_math_class::MathClass;
|
||||
|
||||
@ -13,7 +12,7 @@ use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
||||
use crate::layout::{
|
||||
AlignElem, Alignment, BlockElem, OuterHAlignment, SpecificAlignment, VAlignment,
|
||||
};
|
||||
use crate::math::MathSize;
|
||||
use crate::math::{MathSize, MathVariant};
|
||||
use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement};
|
||||
use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
|
||||
|
||||
@ -112,7 +111,7 @@ pub struct EquationElem {
|
||||
/// The style variant to select.
|
||||
#[internal]
|
||||
#[ghost]
|
||||
pub variant: Option<MathVariant>,
|
||||
pub variant: MathVariant,
|
||||
|
||||
/// Affects the height of exponents.
|
||||
#[internal]
|
||||
@ -129,7 +128,7 @@ pub struct EquationElem {
|
||||
/// Whether to use italic glyphs.
|
||||
#[internal]
|
||||
#[ghost]
|
||||
pub italic: Option<bool>,
|
||||
pub italic: Smart<bool>,
|
||||
|
||||
/// A forced class to use for all fragment.
|
||||
#[internal]
|
||||
|
@ -80,7 +80,6 @@ pub fn module() -> Module {
|
||||
math.define_func::<italic>();
|
||||
math.define_func::<serif>();
|
||||
math.define_func::<sans>();
|
||||
math.define_func::<scr>();
|
||||
math.define_func::<cal>();
|
||||
math.define_func::<frak>();
|
||||
math.define_func::<mono>();
|
||||
|
@ -1,6 +1,4 @@
|
||||
use codex::styling::MathVariant;
|
||||
|
||||
use crate::foundations::{func, Cast, Content};
|
||||
use crate::foundations::{func, Cast, Content, Smart};
|
||||
use crate::math::EquationElem;
|
||||
|
||||
/// Bold font style in math.
|
||||
@ -26,7 +24,7 @@ pub fn upright(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::italic, Some(false))
|
||||
body.set(EquationElem::italic, Smart::Custom(false))
|
||||
}
|
||||
|
||||
/// Italic font style in math.
|
||||
@ -37,7 +35,7 @@ pub fn italic(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::italic, Some(true))
|
||||
body.set(EquationElem::italic, Smart::Custom(true))
|
||||
}
|
||||
|
||||
/// Serif (roman) font style in math.
|
||||
@ -48,7 +46,7 @@ pub fn serif(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::Plain))
|
||||
body.set(EquationElem::variant, MathVariant::Serif)
|
||||
}
|
||||
|
||||
/// Sans-serif font style in math.
|
||||
@ -61,39 +59,23 @@ pub fn sans(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::SansSerif))
|
||||
body.set(EquationElem::variant, MathVariant::Sans)
|
||||
}
|
||||
|
||||
/// Calligraphic (chancery) font style in math.
|
||||
/// Calligraphic font style in math.
|
||||
///
|
||||
/// ```example
|
||||
/// Let $cal(P)$ be the set of ...
|
||||
/// ```
|
||||
///
|
||||
/// This is the default calligraphic/script style for most math fonts. See
|
||||
/// [`scr`]($math.scr) for more on how to get the other style (roundhand).
|
||||
#[func(title = "Calligraphic", keywords = ["mathcal", "chancery"])]
|
||||
pub fn cal(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::Chancery))
|
||||
}
|
||||
|
||||
/// Script (roundhand) font style in math.
|
||||
/// This corresponds both to LaTeX's `\mathcal` and `\mathscr` as both of these
|
||||
/// styles share the same Unicode codepoints. Switching between the styles is
|
||||
/// thus only possible if supported by the font via
|
||||
/// [font features]($text.features).
|
||||
///
|
||||
/// ```example
|
||||
/// $ scr(S) $
|
||||
/// ```
|
||||
///
|
||||
/// There are two ways that fonts can support differentiating `cal` and `scr`.
|
||||
/// The first is using Unicode variation sequences. This works out of the box
|
||||
/// in Typst, however only a few math fonts currently support this.
|
||||
///
|
||||
/// The other way is using [font features]($text.features). For example, the
|
||||
/// roundhand style might be available in a font through the `ss01` feature.
|
||||
/// To use it in Typst, you could then define your own version of `scr` like
|
||||
/// this:
|
||||
/// For the default math font, the roundhand style is available through the
|
||||
/// `ss01` feature. Therefore, you could define your own version of `\mathscr`
|
||||
/// like this:
|
||||
///
|
||||
/// ```example
|
||||
/// #let scr(it) = text(
|
||||
@ -106,12 +88,12 @@ pub fn cal(
|
||||
///
|
||||
/// (The box is not conceptually necessary, but unfortunately currently needed
|
||||
/// due to limitations in Typst's text style handling in math.)
|
||||
#[func(title = "Script Style", keywords = ["mathscr", "roundhand"])]
|
||||
pub fn scr(
|
||||
#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])]
|
||||
pub fn cal(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::Roundhand))
|
||||
body.set(EquationElem::variant, MathVariant::Cal)
|
||||
}
|
||||
|
||||
/// Fraktur font style in math.
|
||||
@ -124,7 +106,7 @@ pub fn frak(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::Fraktur))
|
||||
body.set(EquationElem::variant, MathVariant::Frak)
|
||||
}
|
||||
|
||||
/// Monospace font style in math.
|
||||
@ -137,7 +119,7 @@ pub fn mono(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::Monospace))
|
||||
body.set(EquationElem::variant, MathVariant::Mono)
|
||||
}
|
||||
|
||||
/// Blackboard bold (double-struck) font style in math.
|
||||
@ -155,7 +137,7 @@ pub fn bb(
|
||||
/// The content to style.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.set(EquationElem::variant, Some(MathVariant::DoubleStruck))
|
||||
body.set(EquationElem::variant, MathVariant::Bb)
|
||||
}
|
||||
|
||||
/// Forced display style in math.
|
||||
@ -258,3 +240,15 @@ pub enum MathSize {
|
||||
/// Math on its own line.
|
||||
Display,
|
||||
}
|
||||
|
||||
/// A mathematical style variant, as defined by Unicode.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Cast, Hash)]
|
||||
pub enum MathVariant {
|
||||
#[default]
|
||||
Serif,
|
||||
Sans,
|
||||
Cal,
|
||||
Frak,
|
||||
Mono,
|
||||
Bb,
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ pub use self::raster::{
|
||||
};
|
||||
pub use self::svg::SvgImage;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -16,16 +15,14 @@ use ecow::EcoString;
|
||||
use typst_syntax::{Span, Spanned};
|
||||
use typst_utils::LazyHash;
|
||||
|
||||
use crate::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::diag::StrResult;
|
||||
use crate::foundations::{
|
||||
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
|
||||
StyleChain,
|
||||
};
|
||||
use crate::layout::{Length, Rel, Sizing};
|
||||
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
||||
use crate::model::Figurable;
|
||||
use crate::text::{families, LocalName};
|
||||
use crate::text::LocalName;
|
||||
|
||||
/// A raster or vector graphic.
|
||||
///
|
||||
@ -220,81 +217,6 @@ impl ImageElem {
|
||||
}
|
||||
}
|
||||
|
||||
impl Packed<ImageElem> {
|
||||
/// Decodes the image.
|
||||
pub fn decode(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Image> {
|
||||
let span = self.span();
|
||||
let loaded = &self.source.derived;
|
||||
let format = self.determine_format(styles).at(span)?;
|
||||
|
||||
// Warn the user if the image contains a foreign object. Not perfect
|
||||
// because the svg could also be encoded, but that's an edge case.
|
||||
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
||||
let has_foreign_object =
|
||||
memchr::memmem::find(&loaded.data, b"<foreignObject").is_some();
|
||||
|
||||
if has_foreign_object {
|
||||
engine.sink.warn(warning!(
|
||||
span,
|
||||
"image contains foreign object";
|
||||
hint: "SVG images with foreign objects might render incorrectly in typst";
|
||||
hint: "see https://github.com/typst/typst/issues/1421 for more information"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the image itself.
|
||||
let kind = match format {
|
||||
ImageFormat::Raster(format) => ImageKind::Raster(
|
||||
RasterImage::new(
|
||||
loaded.data.clone(),
|
||||
format,
|
||||
self.icc.get_ref(styles).as_ref().map(|icc| icc.derived.clone()),
|
||||
)
|
||||
.at(span)?,
|
||||
),
|
||||
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
||||
SvgImage::with_fonts(
|
||||
loaded.data.clone(),
|
||||
engine.world,
|
||||
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
||||
)
|
||||
.within(loaded)?,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles)))
|
||||
}
|
||||
|
||||
/// Tries to determine the image format based on the format that was
|
||||
/// explicitly defined, or else the extension, or else the data.
|
||||
fn determine_format(&self, styles: StyleChain) -> StrResult<ImageFormat> {
|
||||
if let Smart::Custom(v) = self.format.get(styles) {
|
||||
return Ok(v);
|
||||
};
|
||||
|
||||
let Derived { source, derived: loaded } = &self.source;
|
||||
if let DataSource::Path(path) = source {
|
||||
let ext = std::path::Path::new(path.as_str())
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
match ext.as_str() {
|
||||
"png" => return Ok(ExchangeFormat::Png.into()),
|
||||
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
|
||||
"gif" => return Ok(ExchangeFormat::Gif.into()),
|
||||
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
|
||||
"webp" => return Ok(ExchangeFormat::Webp.into()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImageFormat::detect(&loaded.data).ok_or("unknown image format")?)
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalName for Packed<ImageElem> {
|
||||
const KEY: &'static str = "figure";
|
||||
}
|
||||
|
@ -18,24 +18,18 @@ impl SVGRenderer {
|
||||
self.xml.write_attribute("width", &size.x.to_pt());
|
||||
self.xml.write_attribute("height", &size.y.to_pt());
|
||||
self.xml.write_attribute("preserveAspectRatio", "none");
|
||||
if let Some(value) = convert_image_scaling(image.scaling()) {
|
||||
self.xml
|
||||
.write_attribute("style", &format_args!("image-rendering: {value}"))
|
||||
}
|
||||
self.xml.end_element();
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an image scaling to a CSS `image-rendering` propery value.
|
||||
pub fn convert_image_scaling(scaling: Smart<ImageScaling>) -> Option<&'static str> {
|
||||
match scaling {
|
||||
Smart::Auto => None,
|
||||
match image.scaling() {
|
||||
Smart::Auto => {}
|
||||
Smart::Custom(ImageScaling::Smooth) => {
|
||||
// This is still experimental and not implemented in all major browsers.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
|
||||
Some("smooth")
|
||||
self.xml.write_attribute("style", "image-rendering: smooth")
|
||||
}
|
||||
Smart::Custom(ImageScaling::Pixelated) => Some("pixelated"),
|
||||
Smart::Custom(ImageScaling::Pixelated) => {
|
||||
self.xml.write_attribute("style", "image-rendering: pixelated")
|
||||
}
|
||||
}
|
||||
self.xml.end_element();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,6 @@ mod paint;
|
||||
mod shape;
|
||||
mod text;
|
||||
|
||||
pub use image::{convert_image_scaling, convert_image_to_base64_url};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Display, Formatter, Write};
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
title: Variants
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["serif", "sans", "frak", "mono", "bb", "cal", "scr"]
|
||||
filter: ["serif", "sans", "frak", "mono", "bb", "cal"]
|
||||
details: |
|
||||
Alternate typefaces within formulas.
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAA3ADcAAD/4QBiRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAEAAAITAAMAAAABAAEAAAAAAAAAAADcAAAAAQAAANwAAAAB/8AACwgAUAAwAQERAP/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAkLicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/aAAgBAQAAPwD5/ooooooooooooor7+ooor4Bor7+ooor4Booor7+or4Booor7+or4Bor7+ooor4Bor7+ooor4Bor7+ooor4Bor7+ooor/2Q==" alt="The letter F"></body>
|
||||
</html>
|
@ -1,10 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: flex; flex-direction: row; gap: 4pt"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAKUlEQVR4AQEeAOH/AP8AAAD/AAAA/wCAAAAAgAAAAIAAgIAAAICAgACAcFMHfiTGz0oAAAAASUVORK5CYII=" style="width: 28.346456692913385pt"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAKUlEQVR4AQEeAOH/AP8AAAD/AAAA/wCAAAAAgAAAAIAAgIAAAICAgACAcFMHfiTGz0oAAAAASUVORK5CYII=" style="image-rendering: smooth; width: 28.346456692913385pt"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAKUlEQVR4AQEeAOH/AP8AAAD/AAAA/wCAAAAAgAAAAIAAgIAAAICAgACAcFMHfiTGz0oAAAAASUVORK5CYII=" style="image-rendering: pixelated; width: 28.346456692913385pt"></div>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 935 B |
Binary file not shown.
Before Width: | Height: | Size: 489 B After Width: | Height: | Size: 296 B |
Binary file not shown.
Before Width: | Height: | Size: 585 B |
@ -12,15 +12,6 @@ $A, italic(A), upright(A), bold(A), bold(upright(A)), \
|
||||
bb("hello") + bold(cal("world")), \
|
||||
mono("SQRT")(x) wreath mono(123 + 456)$
|
||||
|
||||
--- math-style-fallback ---
|
||||
// Test how math styles fallback.
|
||||
$upright(frak(bold(alpha))) = upright(bold(alpha)) \
|
||||
bold(mono(ϝ)) = bold(ϝ) \
|
||||
sans(Theta) = bold(sans(Theta)) \
|
||||
bold(upright(planck)) != planck \
|
||||
bb(e) != italic(bb(e)) \
|
||||
serif(sans(A)) != serif(A)$
|
||||
|
||||
--- math-style-dotless ---
|
||||
// Test styling dotless i and j.
|
||||
$ dotless.i dotless.j,
|
||||
@ -47,15 +38,7 @@ $bb(Gamma) , bb(gamma), bb(Pi), bb(pi), bb(sum)$
|
||||
|
||||
--- math-style-hebrew-exceptions ---
|
||||
// Test hebrew exceptions.
|
||||
$aleph, beth, gimel, daleth$ \
|
||||
$upright(aleph), upright(beth), upright(gimel), upright(daleth)$
|
||||
|
||||
--- math-style-script ---
|
||||
// Test variation selectors for scr and cal.
|
||||
$cal(A) scr(A) bold(cal(O)) scr(bold(O))$
|
||||
|
||||
#show math.equation: set text(font: "Noto Sans Math")
|
||||
$scr(E) cal(E) bold(scr(Y)) cal(bold(Y))$
|
||||
$aleph, beth, gimel, daleth$
|
||||
|
||||
--- issue-3650-italic-equation ---
|
||||
_abc $sin(x) "abc"$_ \
|
||||
|
@ -9,9 +9,6 @@
|
||||
#set page(height: 60pt)
|
||||
#image("/assets/images/tiger.jpg")
|
||||
|
||||
--- image-jpg-html-base64 html ---
|
||||
#image("/assets/images/f2t.jpg", alt: "The letter F")
|
||||
|
||||
--- image-sizing ---
|
||||
// Test configuring the size and fitting behaviour of images.
|
||||
|
||||
@ -131,7 +128,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
||||
width: 1cm,
|
||||
)
|
||||
|
||||
--- image-scaling-methods render html ---
|
||||
--- image-scaling-methods ---
|
||||
#let img(scaling) = image(
|
||||
bytes((
|
||||
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
||||
@ -147,26 +144,14 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
||||
scaling: scaling,
|
||||
)
|
||||
|
||||
#let images = (
|
||||
#stack(
|
||||
dir: ltr,
|
||||
spacing: 4pt,
|
||||
img(auto),
|
||||
img("smooth"),
|
||||
img("pixelated"),
|
||||
)
|
||||
|
||||
#context if target() == "html" {
|
||||
// TODO: Remove this once `stack` is supported in HTML export.
|
||||
html.div(
|
||||
style: "display: flex; flex-direction: row; gap: 4pt",
|
||||
images.join(),
|
||||
)
|
||||
} else {
|
||||
stack(
|
||||
dir: ltr,
|
||||
spacing: 4pt,
|
||||
..images,
|
||||
)
|
||||
}
|
||||
|
||||
--- image-natural-dpi-sizing ---
|
||||
// Test that images aren't upscaled.
|
||||
// Image is just 48x80 at 220dpi. It should not be scaled to fit the page
|
||||
|
Loading…
x
Reference in New Issue
Block a user