Write language and direction for PDFs

This commit is contained in:
Martin Haug 2022-05-11 15:19:03 +02:00
parent a247653cd0
commit 84bd3454df
4 changed files with 52 additions and 6 deletions

View File

@ -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();

View File

@ -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>,
} }

View File

@ -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,

View File

@ -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();