mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Write language and direction for PDFs
This commit is contained in:
parent
a247653cd0
commit
84bd3454df
@ -7,8 +7,8 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
|
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
|
||||||
use pdf_writer::types::{
|
use pdf_writer::types::{
|
||||||
ActionType, AnnotationType, CidFontType, ColorSpaceOperand, FontFlags, SystemInfo,
|
ActionType, AnnotationType, CidFontType, ColorSpaceOperand, Direction, FontFlags,
|
||||||
UnicodeCmap,
|
SystemInfo, UnicodeCmap,
|
||||||
};
|
};
|
||||||
use pdf_writer::writers::ColorSpace;
|
use pdf_writer::writers::ColorSpace;
|
||||||
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr};
|
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr};
|
||||||
@ -18,10 +18,11 @@ use super::subset::subset;
|
|||||||
use crate::font::{find_name, FaceId, FontStore};
|
use crate::font::{find_name, FaceId, FontStore};
|
||||||
use crate::frame::{Element, Frame, Group, Text};
|
use crate::frame::{Element, Frame, Group, Text};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Shape, Size, Stroke,
|
self, Color, Dir, Em, Geometry, Length, Numeric, Paint, Point, Shape, Size, Stroke,
|
||||||
Transform,
|
Transform,
|
||||||
};
|
};
|
||||||
use crate::image::{Image, ImageId, ImageStore, RasterImage};
|
use crate::image::{Image, ImageId, ImageStore, RasterImage};
|
||||||
|
use crate::library::text::Lang;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Export a collection of frames into a PDF file.
|
/// Export a collection of frames into a PDF file.
|
||||||
@ -303,6 +304,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
// The page objects (non-root nodes in the page tree).
|
// The page objects (non-root nodes in the page tree).
|
||||||
let mut page_refs = vec![];
|
let mut page_refs = vec![];
|
||||||
|
let mut languages = HashMap::new();
|
||||||
for page in self.pages {
|
for page in self.pages {
|
||||||
let page_id = self.alloc.bump();
|
let page_id = self.alloc.bump();
|
||||||
let content_id = self.alloc.bump();
|
let content_id = self.alloc.bump();
|
||||||
@ -330,6 +332,13 @@ impl<'a> PdfExporter<'a> {
|
|||||||
annotations.finish();
|
annotations.finish();
|
||||||
page_writer.finish();
|
page_writer.finish();
|
||||||
|
|
||||||
|
for (lang, count) in page.languages {
|
||||||
|
languages
|
||||||
|
.entry(lang)
|
||||||
|
.and_modify(|x| *x += count)
|
||||||
|
.or_insert_with(|| count);
|
||||||
|
}
|
||||||
|
|
||||||
self.writer
|
self.writer
|
||||||
.stream(content_id, &deflate(&page.content.finish()))
|
.stream(content_id, &deflate(&page.content.finish()))
|
||||||
.filter(Filter::FlateDecode);
|
.filter(Filter::FlateDecode);
|
||||||
@ -359,9 +368,28 @@ impl<'a> PdfExporter<'a> {
|
|||||||
resources.finish();
|
resources.finish();
|
||||||
pages.finish();
|
pages.finish();
|
||||||
|
|
||||||
|
let lang = languages
|
||||||
|
.into_iter()
|
||||||
|
.max_by(|(_, v1), (_, v2)| v1.cmp(v2))
|
||||||
|
.map(|(k, _)| k);
|
||||||
|
|
||||||
|
let dir = if lang.map(Lang::dir) == Some(Dir::RTL) {
|
||||||
|
Direction::R2L
|
||||||
|
} else {
|
||||||
|
Direction::L2R
|
||||||
|
};
|
||||||
|
|
||||||
// Write the document information, catalog and wrap it up!
|
// Write the document information, catalog and wrap it up!
|
||||||
self.writer.document_info(self.alloc.bump()).creator(TextStr("Typst"));
|
self.writer.document_info(self.alloc.bump()).creator(TextStr("Typst"));
|
||||||
self.writer.catalog(self.alloc.bump()).pages(page_tree_ref);
|
let mut catalog = self.writer.catalog(self.alloc.bump());
|
||||||
|
catalog.pages(page_tree_ref);
|
||||||
|
catalog.viewer_preferences().direction(dir);
|
||||||
|
|
||||||
|
if let Some(lang) = lang {
|
||||||
|
catalog.lang(TextStr(lang.as_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog.finish();
|
||||||
self.writer.finish()
|
self.writer.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,6 +400,7 @@ struct PageExporter<'a> {
|
|||||||
font_map: &'a mut Remapper<FaceId>,
|
font_map: &'a mut Remapper<FaceId>,
|
||||||
image_map: &'a mut Remapper<ImageId>,
|
image_map: &'a mut Remapper<ImageId>,
|
||||||
glyphs: &'a mut HashMap<FaceId, HashSet<u16>>,
|
glyphs: &'a mut HashMap<FaceId, HashSet<u16>>,
|
||||||
|
languages: HashMap<Lang, usize>,
|
||||||
bottom: f32,
|
bottom: f32,
|
||||||
content: Content,
|
content: Content,
|
||||||
links: Vec<(String, Rect)>,
|
links: Vec<(String, Rect)>,
|
||||||
@ -384,6 +413,7 @@ struct Page {
|
|||||||
size: Size,
|
size: Size,
|
||||||
content: Content,
|
content: Content,
|
||||||
links: Vec<(String, Rect)>,
|
links: Vec<(String, Rect)>,
|
||||||
|
languages: HashMap<Lang, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simulated graphics state used to deduplicate graphics state changes and
|
/// A simulated graphics state used to deduplicate graphics state changes and
|
||||||
@ -403,6 +433,7 @@ impl<'a> PageExporter<'a> {
|
|||||||
font_map: &mut exporter.face_map,
|
font_map: &mut exporter.face_map,
|
||||||
image_map: &mut exporter.image_map,
|
image_map: &mut exporter.image_map,
|
||||||
glyphs: &mut exporter.glyph_sets,
|
glyphs: &mut exporter.glyph_sets,
|
||||||
|
languages: HashMap::new(),
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
content: Content::new(),
|
content: Content::new(),
|
||||||
links: vec![],
|
links: vec![],
|
||||||
@ -422,6 +453,7 @@ impl<'a> PageExporter<'a> {
|
|||||||
size: frame.size,
|
size: frame.size,
|
||||||
content: self.content,
|
content: self.content,
|
||||||
links: self.links,
|
links: self.links,
|
||||||
|
languages: self.languages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,6 +540,11 @@ impl<'a> PageExporter<'a> {
|
|||||||
items.show(Str(&encoded));
|
items.show(Str(&encoded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.languages
|
||||||
|
.entry(text.lang)
|
||||||
|
.and_modify(|x| *x += text.glyphs.len())
|
||||||
|
.or_insert_with(|| text.glyphs.len());
|
||||||
|
|
||||||
items.finish();
|
items.finish();
|
||||||
positioned.finish();
|
positioned.finish();
|
||||||
self.content.end_text();
|
self.content.end_text();
|
||||||
|
@ -8,6 +8,7 @@ use crate::geom::{
|
|||||||
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
||||||
};
|
};
|
||||||
use crate::image::ImageId;
|
use crate::image::ImageId;
|
||||||
|
use crate::library::text::Lang;
|
||||||
use crate::util::{EcoString, MaybeShared};
|
use crate::util::{EcoString, MaybeShared};
|
||||||
|
|
||||||
/// A finished layout with elements at fixed positions.
|
/// A finished layout with elements at fixed positions.
|
||||||
@ -269,6 +270,8 @@ pub struct Text {
|
|||||||
pub size: Length,
|
pub size: Length,
|
||||||
/// Glyph color.
|
/// Glyph color.
|
||||||
pub fill: Paint,
|
pub fill: Paint,
|
||||||
|
/// The natural language of the text.
|
||||||
|
pub lang: Lang,
|
||||||
/// The glyphs.
|
/// The glyphs.
|
||||||
pub glyphs: Vec<Glyph>,
|
pub glyphs: Vec<Glyph>,
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl Lang {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The default direction for the language.
|
/// The default direction for the language.
|
||||||
pub fn dir(&self) -> Dir {
|
pub fn dir(self) -> Dir {
|
||||||
match self.as_str() {
|
match self.as_str() {
|
||||||
"ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur"
|
"ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur"
|
||||||
| "yi" => Dir::RTL,
|
| "yi" => Dir::RTL,
|
||||||
|
@ -104,7 +104,13 @@ impl<'a> ShapedText<'a> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let text = Text { face_id, size: self.size, fill, glyphs };
|
let text = Text {
|
||||||
|
face_id,
|
||||||
|
size: self.size,
|
||||||
|
lang: self.styles.get(TextNode::LANG),
|
||||||
|
fill,
|
||||||
|
glyphs,
|
||||||
|
};
|
||||||
let text_layer = frame.layer();
|
let text_layer = frame.layer();
|
||||||
let width = text.width();
|
let width = text.width();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user