1use super::dom::{ElementData, NodeData, NodeRef, TextData, WRAPPER_TAG_NAME};
2use super::layout::{ChildArtifact, GlueMaterial, LoopContext, PenaltyMaterial, SiblingStyle};
3use super::layout::{DEFAULT_HYPH_LANG, HYPHENATION_PATTERNS, collapse_margins, hyph_lang};
4use super::layout::{Display, Float, ImageElement, ParagraphElement, TextAlign, TextElement};
5use super::layout::{DrawCommand, DrawState, FontKind, Fonts, ImageCommand, RootData, TextCommand};
6use super::layout::{EM_SPACE_RATIOS, FONT_SPACES, WORD_SPACE_RATIOS};
7use super::layout::{ImageMaterial, InlineMaterial, StyleData, TextMaterial};
8use super::layout::{LineStats, ListStyleType, WordSpacing};
9use super::parse::{parse_color, parse_line_height, parse_list_style_type, parse_vertical_align};
10use super::parse::{parse_display, parse_edge, parse_float, parse_text_align, parse_text_indent};
11use super::parse::{parse_font_features, parse_font_size, parse_font_variant, parse_font_weight};
12use super::parse::{
13 parse_font_kind, parse_font_style, parse_height, parse_inline_material, parse_width,
14};
15use super::parse::{parse_letter_spacing, parse_word_spacing};
16use super::style::{StyleSheet, specified_values};
17use super::xml::XmlExt;
18use crate::device::CURRENT_DEVICE;
19use crate::document::pdf::PdfOpener;
20use crate::document::{Document, Location};
21use crate::font::{FontFamily, FontOpener};
22use crate::framebuffer::{Framebuffer, Pixmap};
23use crate::geom::{Edge, Point, Rectangle, Vec2};
24use crate::helpers::{Normalize, decode_entities};
25use crate::settings::{
26 DEFAULT_FONT_SIZE, DEFAULT_LINE_HEIGHT, DEFAULT_MARGIN_WIDTH, DEFAULT_TEXT_ALIGN,
27};
28use crate::settings::{HYPHEN_PENALTY, STRETCH_TOLERANCE};
29use crate::unit::{mm_to_px, pt_to_px};
30use anyhow::Error;
31use kl_hyphenate::{Hyphenator, Iter, Standard};
32use paragraph_breaker::{Breakpoint, INFINITE_PENALTY, Item as ParagraphItem};
33use paragraph_breaker::{standard_fit, total_fit};
34use percent_encoding::percent_decode_str;
35use septem::Roman;
36use std::convert::TryFrom;
37use std::path::PathBuf;
38use xi_unicode::LineBreakIterator;
39
40const DEFAULT_DPI: u16 = 300;
41const DEFAULT_WIDTH: u32 = 1404;
42const DEFAULT_HEIGHT: u32 = 1872;
43
44pub type Page = Vec<DrawCommand>;
45
46pub trait ResourceFetcher {
47 fn fetch(&mut self, name: &str) -> Result<Vec<u8>, Error>;
48}
49
50pub struct Engine {
52 fonts: Option<Fonts>,
54 hyphen_penalty: i32,
56 stretch_tolerance: f32,
58 pub margin: Edge,
60 pub font_size: f32,
62 pub text_align: TextAlign,
64 pub line_height: f32,
66 pub dims: (u32, u32),
68 pub dpi: u16,
70}
71
72impl Engine {
73 pub fn new() -> Engine {
74 let margin =
75 Edge::uniform(mm_to_px(DEFAULT_MARGIN_WIDTH as f32, DEFAULT_DPI).round() as i32);
76 let line_height = DEFAULT_LINE_HEIGHT;
77
78 Engine {
79 fonts: None,
80 hyphen_penalty: HYPHEN_PENALTY,
81 stretch_tolerance: STRETCH_TOLERANCE,
82 margin,
83 font_size: DEFAULT_FONT_SIZE,
84 text_align: DEFAULT_TEXT_ALIGN,
85 line_height,
86 dims: (DEFAULT_WIDTH, DEFAULT_HEIGHT),
87 dpi: DEFAULT_DPI,
88 }
89 }
90
91 #[inline]
92 pub fn load_fonts(&mut self) {
93 if self.fonts.is_none() {
94 self.fonts = build_fonts(None).ok();
95 }
96 }
97
98 pub fn load_fonts_from(&mut self, root_dir: std::path::PathBuf) {
99 if self.fonts.is_none() {
100 self.fonts = build_fonts(Some(root_dir)).ok();
101 }
102 }
103
104 pub fn set_hyphen_penalty(&mut self, hyphen_penalty: i32) {
105 self.hyphen_penalty = hyphen_penalty;
106 }
107
108 pub fn set_stretch_tolerance(&mut self, stretch_tolerance: f32) {
109 self.stretch_tolerance = stretch_tolerance;
110 }
111
112 pub fn set_margin(&mut self, margin: &Edge) {
113 self.margin = *margin;
114 }
115
116 pub fn set_font_size(&mut self, font_size: f32) {
117 self.font_size = font_size;
118 }
119
120 pub fn layout(&mut self, width: u32, height: u32, font_size: f32, dpi: u16) {
121 self.dims = (width, height);
123 self.dpi = dpi;
124 self.font_size = font_size;
125 }
126
127 pub fn set_text_align(&mut self, text_align: TextAlign) {
128 self.text_align = text_align;
129 }
130
131 pub fn set_font_family(&mut self, family_name: &str, search_path: &str) {
132 if let Ok(serif_family) = FontFamily::from_name(family_name, search_path) {
133 self.load_fonts();
134 if let Some(fonts) = self.fonts.as_mut() {
135 fonts.serif = serif_family;
136 }
137 }
138 }
139
140 pub fn set_margin_width(&mut self, width: i32) {
141 self.margin = Edge::uniform(mm_to_px(width as f32, self.dpi).round() as i32);
142 }
143
144 pub fn set_line_height(&mut self, line_height: f32) {
145 self.line_height = line_height;
146 }
147
148 #[inline]
149 pub fn rect(&self) -> Rectangle {
150 let (width, height) = self.dims;
151 rect![0, 0, width as i32, height as i32]
152 }
153
154 pub fn build_display_list(
155 &mut self,
156 node: NodeRef,
157 parent_style: &StyleData,
158 loop_context: &LoopContext,
159 stylesheet: &StyleSheet,
160 root_data: &RootData,
161 resource_fetcher: &mut dyn ResourceFetcher,
162 draw_state: &mut DrawState,
163 display_list: &mut Vec<Page>,
164 ) -> ChildArtifact {
165 let mut style = StyleData::default();
167 let mut rects: Vec<Option<Rectangle>> = vec![None];
168
169 let props = specified_values(node, stylesheet);
170
171 style.display = props
172 .get("display")
173 .and_then(|value| parse_display(value))
174 .unwrap_or(Display::Block);
175
176 if style.display == Display::None {
177 return ChildArtifact {
178 sibling_style: SiblingStyle {
179 padding: Edge::default(),
180 margin: Edge::default(),
181 },
182 rects: Vec::new(),
183 };
184 }
185
186 style.font_style = parent_style.font_style;
187 style.line_height = parent_style.line_height;
188 style.retain_whitespace = parent_style.retain_whitespace;
189
190 match node.tag_name() {
191 Some("pre") => style.retain_whitespace = true,
192 Some("li") | Some(WRAPPER_TAG_NAME) => {
193 style.list_style_type = parent_style.list_style_type
194 }
195 Some("table") => {
196 let position = draw_state.position;
197 draw_state.column_widths.clear();
198 draw_state.min_column_widths.clear();
199 draw_state.max_column_widths.clear();
200 draw_state.center_table = style.display == Display::InlineTable
201 && parent_style.text_align == TextAlign::Center;
202 self.compute_column_widths(
203 node,
204 parent_style,
205 loop_context,
206 stylesheet,
207 root_data,
208 resource_fetcher,
209 draw_state,
210 );
211 draw_state.position = position;
212 }
213 _ => (),
214 }
215
216 style.language = props
217 .get("lang")
218 .cloned()
219 .or_else(|| parent_style.language.clone());
220
221 style.font_size = props
222 .get("font-size")
223 .and_then(|value| parse_font_size(value, parent_style.font_size, self.font_size))
224 .unwrap_or(parent_style.font_size);
225
226 style.line_height = props
227 .get("line-height")
228 .and_then(|value| parse_line_height(value, style.font_size, self.font_size, self.dpi))
229 .unwrap_or_else(|| {
230 ((style.font_size / parent_style.font_size) * parent_style.line_height as f32)
231 .round() as i32
232 });
233
234 style.letter_spacing = props
235 .get("letter-spacing")
236 .and_then(|value| {
237 parse_letter_spacing(value, style.font_size, self.font_size, self.dpi)
238 })
239 .unwrap_or(parent_style.letter_spacing);
240
241 style.word_spacing = props
242 .get("word-spacing")
243 .and_then(|value| parse_word_spacing(value, style.font_size, self.font_size, self.dpi))
244 .unwrap_or(parent_style.word_spacing);
245
246 style.vertical_align = props
247 .get("vertical-align")
248 .and_then(|value| {
249 parse_vertical_align(
250 value,
251 style.font_size,
252 self.font_size,
253 style.line_height,
254 self.dpi,
255 )
256 })
257 .unwrap_or(parent_style.vertical_align);
258
259 style.font_kind = props
260 .get("font-family")
261 .and_then(|value| parse_font_kind(value))
262 .unwrap_or(parent_style.font_kind);
263
264 style.font_style = props
265 .get("font-style")
266 .and_then(|value| parse_font_style(value))
267 .unwrap_or(parent_style.font_style);
268
269 style.font_weight = props
270 .get("font-weight")
271 .and_then(|value| parse_font_weight(value))
272 .unwrap_or(parent_style.font_weight);
273
274 style.color = props
275 .get("color")
276 .and_then(|value| parse_color(value))
277 .unwrap_or(parent_style.color);
278
279 style.text_indent = props
280 .get("text-indent")
281 .and_then(|value| {
282 parse_text_indent(
283 value,
284 style.font_size,
285 self.font_size,
286 parent_style.width,
287 self.dpi,
288 )
289 })
290 .unwrap_or(parent_style.text_indent);
291
292 style.text_align = props
293 .get("text-align")
294 .map(String::as_str)
295 .or_else(|| node.attribute("align"))
296 .and_then(|value| parse_text_align(value))
297 .unwrap_or(parent_style.text_align);
298
299 style.font_features = props
300 .get("font-feature-settings")
301 .map(|value| parse_font_features(value))
302 .or_else(|| parent_style.font_features.clone());
303
304 if let Some(value) = props
305 .get("list-style-type")
306 .map(|value| parse_list_style_type(value))
307 {
308 style.list_style_type = value;
309 }
310
311 if let Some(value) = props.get("font-variant") {
312 let mut features = parse_font_variant(value);
313 if let Some(v) = style.font_features.as_mut() {
314 v.append(&mut features);
315 }
316 }
317
318 if node.parent().is_some() {
319 style.margin = parse_edge(
320 props.get("margin-top").map(String::as_str),
321 props.get("margin-right").map(String::as_str),
322 props.get("margin-bottom").map(String::as_str),
323 props.get("margin-left").map(String::as_str),
324 style.font_size,
325 self.font_size,
326 parent_style.width,
327 self.dpi,
328 );
329
330 style.margin.top =
332 collapse_margins(loop_context.sibling_style.margin.bottom, style.margin.top);
333
334 if loop_context.is_first {
336 style.margin.top = collapse_margins(parent_style.margin.top, style.margin.top);
337 }
338
339 style.padding = parse_edge(
340 props.get("padding-top").map(String::as_str),
341 props.get("padding-right").map(String::as_str),
342 props.get("padding-bottom").map(String::as_str),
343 props.get("padding-left").map(String::as_str),
344 style.font_size,
345 self.font_size,
346 parent_style.width,
347 self.dpi,
348 );
349 }
350
351 style.width = props
352 .get("width")
353 .and_then(|value| {
354 parse_width(
355 value,
356 style.font_size,
357 self.font_size,
358 parent_style.width,
359 self.dpi,
360 )
361 })
362 .unwrap_or(0);
363
364 style.height = props
365 .get("height")
366 .and_then(|value| {
367 parse_height(
368 value,
369 style.font_size,
370 self.font_size,
371 parent_style.width,
372 self.dpi,
373 )
374 })
375 .unwrap_or(0);
376
377 style.start_x = parent_style.start_x + style.margin.left + style.padding.left;
378 style.end_x = parent_style.end_x - style.margin.right - style.padding.right;
379
380 let mut width = style.end_x - style.start_x;
381
382 if width < 0 {
383 if style.width > 0 {
384 let total_space = style.margin.left
385 + style.padding.left
386 + style.margin.right
387 + style.padding.right;
388 let remaining_space = parent_style.width - style.width;
389 let ratio = remaining_space as f32 / total_space as f32;
390 style.margin.left = (style.margin.left as f32 * ratio).round() as i32;
391 style.padding.left = (style.padding.left as f32 * ratio).round() as i32;
392 style.margin.right = (style.margin.right as f32 * ratio).round() as i32;
393 style.padding.right = (style.padding.right as f32 * ratio).round() as i32;
394 style.start_x = parent_style.start_x + style.margin.left + style.padding.left;
395 style.end_x = parent_style.end_x - style.margin.right - style.padding.right;
396 width = style.width;
397 } else {
398 style.margin.left = 0;
399 style.padding.left = 0;
400 style.margin.right = 0;
401 style.padding.right = 0;
402 style.start_x = parent_style.start_x;
403 style.end_x = parent_style.end_x;
404 width = parent_style.width;
405 }
406 }
407
408 style.width = width;
409
410 if props.get("page-break-before").map(String::as_str) == Some("always") {
411 display_list.push(Vec::new());
412 draw_state.position.y = root_data.rect.min.y;
413 }
414
415 draw_state.position.y += style.padding.top;
416
417 let has_blocks = node.children().any(|n| n.is_block());
418
419 if has_blocks {
420 if node.id().is_some() {
421 display_list
422 .last_mut()
423 .unwrap()
424 .push(DrawCommand::Marker(root_data.start_offset + node.offset()));
425 }
426 if node.has_children() {
427 let mut inner_loop_context = LoopContext::default();
428
429 if node.tag_name() == Some("tr") {
430 inner_loop_context.is_first = loop_context.is_first;
431 inner_loop_context.is_last = loop_context.is_last;
432
433 if draw_state.column_widths.is_empty() {
434 let min_row_width: i32 = draw_state.min_column_widths.iter().sum();
435 let max_row_width: i32 = draw_state.max_column_widths.iter().sum();
436 if min_row_width >= width {
438 draw_state.column_widths = draw_state
439 .min_column_widths
440 .iter()
441 .map(|w| {
442 ((*w as f32 / min_row_width as f32) * width as f32).round()
443 as i32
444 })
445 .collect();
446 } else if max_row_width <= width {
447 draw_state.column_widths = draw_state.max_column_widths.clone();
448 } else {
449 let dw = (width - min_row_width) as f32;
450 let dr = (max_row_width - min_row_width) as f32;
451 let gf = dw / dr;
452 draw_state.column_widths = draw_state
453 .min_column_widths
454 .iter()
455 .zip(draw_state.max_column_widths.iter())
456 .map(|(a, b)| a + ((b - a) as f32 * gf).round() as i32)
457 .collect();
458 }
459 }
460
461 if draw_state.center_table {
462 let actual_width = draw_state.column_widths.iter().sum();
463 let delta_width = width - actual_width;
464 let left_shift = delta_width / 2;
465 let right_shift = delta_width - left_shift;
466 style.start_x += left_shift;
467 style.end_x -= right_shift;
468 style.width = actual_width;
469 }
470
471 let start_x = style.start_x;
472 let end_x = style.end_x;
473 let mut cur_x = start_x;
474 let position = draw_state.position;
475 let mut final_page = (0, position);
476 let page_index = display_list.len() - 1;
477 let mut index = 0;
478
479 for child in node.children().filter(|child| child.is_element()) {
481 if index >= draw_state.column_widths.len() {
482 break;
483 }
484
485 let colspan = child
486 .attribute("colspan")
487 .and_then(|v| v.parse().ok())
488 .unwrap_or(1)
489 .min(draw_state.column_widths.len() - index);
490 let column_width = draw_state.column_widths[index..index + colspan]
491 .iter()
492 .sum::<i32>();
493 let mut child_display_list = vec![Vec::new()];
494 style.start_x = cur_x;
495 style.end_x = cur_x + column_width;
496 draw_state.position = position;
497 let artifact = self.build_display_list(
498 child,
499 &style,
500 &inner_loop_context,
501 stylesheet,
502 root_data,
503 resource_fetcher,
504 draw_state,
505 &mut child_display_list,
506 );
507 let pages_count = child_display_list.len();
508 if pages_count > final_page.0
509 || (pages_count == final_page.0
510 && draw_state.position.y > final_page.1.y)
511 {
512 final_page = (pages_count, draw_state.position);
513 }
514
515 for (i, mut pg) in child_display_list.into_iter().enumerate() {
516 if let Some(page) = display_list.get_mut(page_index + i) {
517 page.append(&mut pg);
518 } else {
519 display_list.push(pg);
520 }
521 }
522
523 for (i, rect) in artifact.rects.into_iter().enumerate() {
524 if let Some(page_rect) = rects.get_mut(i) {
525 if let Some(pr) = page_rect.as_mut() {
526 if let Some(r) = rect.as_ref() {
527 pr.absorb(r);
528 }
529 } else {
530 *page_rect = rect;
531 }
532 } else {
533 rects.push(rect);
534 }
535 }
536
537 inner_loop_context.sibling_style = artifact.sibling_style;
538
539 if inner_loop_context.is_last {
540 style.margin.bottom = collapse_margins(
541 inner_loop_context.sibling_style.margin.bottom,
542 style.margin.bottom,
543 );
544 }
545
546 index += colspan;
547 cur_x += column_width;
548 }
549
550 style.start_x = start_x;
551 style.end_x = end_x;
552 draw_state.position = final_page.1;
553 } else {
554 let mut iter = node
555 .children()
556 .filter(|child| child.is_element())
557 .peekable();
558 inner_loop_context.is_first = true;
559 let is_list_item = node.tag_name() == Some("li");
560 let mut index = 0;
561
562 while let Some(child) = iter.next() {
563 if iter.peek().is_none() {
564 inner_loop_context.is_last = true;
565 }
566
567 inner_loop_context.index = index;
568
569 if is_list_item || child.is_wrapper() {
570 inner_loop_context.index = loop_context.index;
571 }
572
573 let artifact = self.build_display_list(
574 child,
575 &style,
576 &inner_loop_context,
577 stylesheet,
578 root_data,
579 resource_fetcher,
580 draw_state,
581 display_list,
582 );
583 inner_loop_context.sibling_style = artifact.sibling_style;
584 inner_loop_context.is_first = false;
585
586 if inner_loop_context.is_last {
588 style.margin.bottom = collapse_margins(
589 inner_loop_context.sibling_style.margin.bottom,
590 style.margin.bottom,
591 );
592 }
593
594 let last_index = rects.len() - 1;
595 for (i, rect) in artifact.rects.into_iter().enumerate() {
596 if let Some(page_rect) = rects.get_mut(last_index + i) {
597 if let Some(pr) = page_rect.as_mut() {
598 if let Some(r) = rect.as_ref() {
599 pr.absorb(r);
600 }
601 } else {
602 *page_rect = rect;
603 }
604 } else {
605 rects.push(rect);
606 }
607 }
608
609 index += 1
610 }
611 }
612 }
613 } else {
614 if node.has_children() {
615 let mut inlines = Vec::new();
616 let mut markers = Vec::new();
617 if node.id().is_some() {
618 markers.push(node.offset());
619 }
620 for child in node.children() {
621 self.gather_inline_material(
622 child,
623 stylesheet,
624 &style,
625 &root_data.spine_dir,
626 &mut markers,
627 &mut inlines,
628 );
629 }
630 if !inlines.is_empty() {
631 draw_state.prefix = match style.list_style_type {
632 None => {
633 let parent = node
634 .ancestor_elements()
635 .find(|n| matches!(n.tag_name(), Some("ul" | "ol")));
636 match parent.and_then(|parent| parent.tag_name()) {
637 Some("ul") => {
638 format_list_prefix(ListStyleType::Disc, loop_context.index)
639 }
640 Some("ol") => {
641 format_list_prefix(ListStyleType::Decimal, loop_context.index)
642 }
643 _ => None,
644 }
645 }
646 Some(kind) => format_list_prefix(kind, loop_context.index),
647 };
648 self.place_paragraphs(
649 &inlines,
650 &style,
651 root_data,
652 &markers,
653 resource_fetcher,
654 draw_state,
655 &mut rects,
656 display_list,
657 );
658 }
659 } else {
660 if node.id().is_some() {
661 display_list
662 .last_mut()
663 .unwrap()
664 .push(DrawCommand::Marker(root_data.start_offset + node.offset()));
665 }
666 }
667 }
668
669 if style.height > 0 {
670 let height = rects
671 .iter()
672 .filter_map(|v| v.map(|r| r.height() as i32))
673 .sum::<i32>();
674 draw_state.position.y += (style.height - height).max(0);
675 }
676
677 if rects.is_empty() || rects == [None] {
679 style.margin.bottom = collapse_margins(style.margin.bottom, style.margin.top);
680 style.margin.top = 0;
681 }
682
683 draw_state.position.y += style.padding.bottom;
684
685 if props.get("page-break-after").map(String::as_str) == Some("always") {
686 display_list.push(Vec::new());
687 draw_state.position.y = root_data.rect.min.y;
688 }
689
690 ChildArtifact {
691 sibling_style: SiblingStyle {
692 padding: style.padding,
693 margin: style.margin,
694 },
695 rects,
696 }
697 }
698
699 fn compute_column_widths(
700 &mut self,
701 node: NodeRef,
702 parent_style: &StyleData,
703 loop_context: &LoopContext,
704 stylesheet: &StyleSheet,
705 root_data: &RootData,
706 resource_fetcher: &mut dyn ResourceFetcher,
707 draw_state: &mut DrawState,
708 ) {
709 if node.tag_name() == Some("tr") {
710 let mut index = 0;
711 for child in node.children().filter(|c| c.is_element()) {
712 let colspan = child
713 .attribute("colspan")
714 .and_then(|v| v.parse().ok())
715 .unwrap_or(1);
716 let mut display_list = vec![Vec::new()];
717 let artifact = self.build_display_list(
718 child,
719 parent_style,
720 loop_context,
721 stylesheet,
722 root_data,
723 resource_fetcher,
724 draw_state,
725 &mut display_list,
726 );
727 let horiz_padding =
728 artifact.sibling_style.padding.left + artifact.sibling_style.padding.right;
729 let min_width = display_list
730 .into_iter()
731 .flatten()
732 .filter_map(|dc| match dc {
733 DrawCommand::Text(TextCommand { rect, .. }) => {
734 Some(rect.width() as i32 + horiz_padding)
735 }
736 DrawCommand::Image(ImageCommand { rect, .. }) => Some(
737 (rect.width() as i32)
738 .min(pt_to_px(parent_style.font_size, self.dpi).round().max(1.0)
739 as i32)
740 + horiz_padding,
741 ),
742 _ => None,
743 })
744 .max()
745 .unwrap_or(0);
746 let max_width = artifact
747 .rects
748 .into_iter()
749 .filter_map(|v| v.map(|r| r.width() as i32 + horiz_padding))
750 .max()
751 .unwrap_or(0);
752 if colspan == 1 {
753 if let Some(cw) = draw_state.min_column_widths.get_mut(index) {
754 *cw = (*cw).max(min_width);
755 } else {
756 draw_state.min_column_widths.push(min_width);
757 }
758 if let Some(cw) = draw_state.max_column_widths.get_mut(index) {
759 *cw = (*cw).max(max_width);
760 } else {
761 draw_state.max_column_widths.push(max_width);
762 }
763 }
764
765 index += colspan;
766 }
767 } else {
768 for child in node.children().filter(|c| c.is_element()) {
769 self.compute_column_widths(
770 child,
771 parent_style,
772 loop_context,
773 stylesheet,
774 root_data,
775 resource_fetcher,
776 draw_state,
777 );
778 }
779 }
780 }
781
782 fn gather_inline_material(
783 &self,
784 node: NodeRef,
785 stylesheet: &StyleSheet,
786 parent_style: &StyleData,
787 spine_dir: &PathBuf,
788 markers: &mut Vec<usize>,
789 inlines: &mut Vec<InlineMaterial>,
790 ) {
791 match node.data() {
792 NodeData::Element(ElementData {
793 offset,
794 name,
795 attributes,
796 ..
797 }) => {
798 let mut style = StyleData::default();
799 let props = specified_values(node, stylesheet);
800
801 style.font_style = parent_style.font_style;
802 style.line_height = parent_style.line_height;
803 style.text_indent = parent_style.text_indent;
804 style.retain_whitespace = parent_style.retain_whitespace;
805 style.language = parent_style.language.clone();
806 style.uri = parent_style.uri.clone();
807
808 style.display = props
809 .get("display")
810 .and_then(|value| parse_display(value))
811 .unwrap_or(Display::Inline);
812
813 if style.display == Display::None {
814 return;
815 }
816
817 style.font_size = props
818 .get("font-size")
819 .and_then(|value| {
820 parse_font_size(value, parent_style.font_size, self.font_size)
821 })
822 .unwrap_or(parent_style.font_size);
823
824 style.width = props
825 .get("width")
826 .and_then(|value| {
827 parse_width(
828 value,
829 style.font_size,
830 self.font_size,
831 parent_style.width,
832 self.dpi,
833 )
834 })
835 .unwrap_or(0);
836
837 style.height = props
838 .get("height")
839 .and_then(|value| {
840 parse_height(
841 value,
842 style.font_size,
843 self.font_size,
844 parent_style.width,
845 self.dpi,
846 )
847 })
848 .unwrap_or(0);
849
850 style.font_kind = props
851 .get("font-family")
852 .and_then(|value| parse_font_kind(value))
853 .unwrap_or(parent_style.font_kind);
854
855 style.color = props
856 .get("color")
857 .and_then(|value| parse_color(value))
858 .unwrap_or(parent_style.color);
859
860 style.letter_spacing = props
861 .get("letter-spacing")
862 .and_then(|value| {
863 parse_letter_spacing(value, style.font_size, self.font_size, self.dpi)
864 })
865 .unwrap_or(parent_style.letter_spacing);
866
867 style.word_spacing = props
868 .get("word-spacing")
869 .and_then(|value| {
870 parse_word_spacing(value, style.font_size, self.font_size, self.dpi)
871 })
872 .unwrap_or(parent_style.word_spacing);
873
874 style.vertical_align = props
875 .get("vertical-align")
876 .and_then(|value| {
877 parse_vertical_align(
878 value,
879 style.font_size,
880 self.font_size,
881 style.line_height,
882 self.dpi,
883 )
884 })
885 .unwrap_or(parent_style.vertical_align);
886
887 style.font_style = props
888 .get("font-style")
889 .and_then(|value| parse_font_style(value))
890 .unwrap_or(parent_style.font_style);
891
892 style.font_weight = props
893 .get("font-weight")
894 .and_then(|value| parse_font_weight(value))
895 .unwrap_or(parent_style.font_weight);
896
897 style.font_features = props
898 .get("font-feature-settings")
899 .map(|value| parse_font_features(value))
900 .or_else(|| parent_style.font_features.clone());
901
902 if let Some(value) = props.get("font-variant") {
903 let mut features = parse_font_variant(value);
904 if let Some(v) = style.font_features.as_mut() {
905 v.append(&mut features);
906 }
907 }
908
909 if node.id().is_some() {
910 markers.push(node.offset());
911 }
912
913 match name.as_ref() {
914 "img" | "image" => {
915 let attr = if name == "img" { "src" } else { "xlink:href" };
916
917 let path = attributes
918 .get(attr)
919 .and_then(|src| {
920 spine_dir.join(src).normalize().to_str().map(|uri| {
921 percent_decode_str(&decode_entities(uri))
922 .decode_utf8_lossy()
923 .into_owned()
924 })
925 })
926 .unwrap_or_default();
927
928 style.float = props.get("float").and_then(|value| parse_float(value));
929
930 let is_block = style.display == Display::Block;
931 if is_block || style.float.is_some() {
932 style.margin = parse_edge(
933 props.get("margin-top").map(String::as_str),
934 props.get("margin-right").map(String::as_str),
935 props.get("margin-bottom").map(String::as_str),
936 props.get("margin-left").map(String::as_str),
937 style.font_size,
938 self.font_size,
939 parent_style.width,
940 self.dpi,
941 );
942 }
943 if is_block {
944 inlines.push(InlineMaterial::LineBreak);
945 }
946 inlines.push(InlineMaterial::Image(ImageMaterial {
947 offset: *offset,
948 path,
949 style,
950 }));
951 if is_block {
952 inlines.push(InlineMaterial::LineBreak);
953 }
954 return;
955 }
956 "a" => {
957 style.uri = attributes.get("href").map(|uri| {
958 percent_decode_str(&decode_entities(uri))
959 .decode_utf8_lossy()
960 .into_owned()
961 });
962 }
963 "br" => {
964 inlines.push(InlineMaterial::LineBreak);
965 return;
966 }
967 _ => {}
968 }
969
970 if let Some(mut v) = props.get("-cadmus-insert-before").map(|value| {
971 parse_inline_material(value, style.font_size, self.font_size, self.dpi)
972 }) {
973 inlines.append(&mut v);
974 }
975
976 for child in node.children() {
977 self.gather_inline_material(
978 child, stylesheet, &style, spine_dir, markers, inlines,
979 );
980 }
981
982 if let Some(mut v) = props.get("-cadmus-insert-after").map(|value| {
983 parse_inline_material(value, style.font_size, self.font_size, self.dpi)
984 }) {
985 inlines.append(&mut v);
986 }
987 }
988 NodeData::Text(TextData { offset, text }) => {
989 inlines.push(InlineMaterial::Text(TextMaterial {
990 offset: *offset,
991 text: decode_entities(text).into_owned(),
992 style: parent_style.clone(),
993 }));
994 }
995 NodeData::Whitespace(TextData { offset, text }) => {
996 inlines.push(InlineMaterial::Text(TextMaterial {
997 offset: *offset,
998 text: text.to_string(),
999 style: parent_style.clone(),
1000 }));
1001 }
1002 _ => (),
1003 }
1004 }
1005
1006 fn make_paragraph_items(
1007 &mut self,
1008 inlines: &[InlineMaterial],
1009 parent_style: &StyleData,
1010 line_width: i32,
1011 resource_fetcher: &mut dyn ResourceFetcher,
1012 ) -> (Vec<ParagraphItem<ParagraphElement>>, Vec<ImageElement>) {
1013 let mut items = Vec::new();
1014 let mut floats = Vec::new();
1015 let big_stretch = 3 * {
1016 let font_size = (parent_style.font_size * 64.0) as u32;
1017 let font = self.fonts.as_mut().unwrap().get_mut(
1018 parent_style.font_kind,
1019 parent_style.font_style,
1020 parent_style.font_weight,
1021 );
1022 font.set_size(font_size, self.dpi);
1023 font.plan(" ", None, None).width
1024 };
1025
1026 if parent_style.text_align == TextAlign::Center {
1027 items.push(ParagraphItem::Box {
1028 width: 0,
1029 data: ParagraphElement::Nothing,
1030 });
1031 items.push(ParagraphItem::Glue {
1032 width: 0,
1033 stretch: big_stretch,
1034 shrink: 0,
1035 });
1036 }
1037
1038 for (index, mater) in inlines.iter().enumerate() {
1039 match mater {
1040 InlineMaterial::Image(ImageMaterial {
1041 offset,
1042 path,
1043 style,
1044 }) => {
1045 let (mut width, mut height) = (style.width, style.height);
1046 let mut scale = 1.0;
1047 let dpi = self.dpi;
1048
1049 if let Ok(buf) = resource_fetcher.fetch(path) {
1050 if let Some(doc) =
1051 PdfOpener::new().and_then(|opener| opener.open_memory(path, &buf))
1052 {
1053 if let Some((w, h)) = doc.dims(0) {
1054 if width == 0 && height == 0 {
1055 width = pt_to_px(w, dpi).round() as i32;
1056 height = pt_to_px(h, dpi).round() as i32;
1057 } else if width != 0 {
1058 height = (width as f32 * h / w).round() as i32;
1059 } else if height != 0 {
1060 width = (height as f32 * w / h).round() as i32;
1061 }
1062 scale = width as f32 / w;
1063 }
1064 }
1065
1066 if width * height > 0 {
1067 let element = ImageElement {
1068 offset: *offset,
1069 width,
1070 height,
1071 scale,
1072 vertical_align: style.vertical_align,
1073 display: style.display,
1074 margin: style.margin,
1075 float: style.float,
1076 path: path.clone(),
1077 uri: style.uri.clone(),
1078 };
1079 if style.float.is_none() {
1080 items.push(ParagraphItem::Box {
1081 width,
1082 data: ParagraphElement::Image(element),
1083 });
1084 } else {
1085 floats.push(element);
1086 }
1087 }
1088 }
1089 }
1090 InlineMaterial::Text(TextMaterial {
1091 offset,
1092 text,
1093 style,
1094 }) => {
1095 let font_size = (style.font_size * 64.0) as u32;
1096 let space_plan = {
1097 let font = self.fonts.as_mut().unwrap().get_mut(
1098 parent_style.font_kind,
1099 parent_style.font_style,
1100 parent_style.font_weight,
1101 );
1102 font.set_size(font_size, self.dpi);
1103 font.plan(" 0.", None, None)
1104 };
1105 let mut start_index = 0;
1106 for (end_index, _is_hardbreak) in LineBreakIterator::new(text) {
1107 for chunk in
1108 text[start_index..end_index].split_inclusive(char::is_whitespace)
1109 {
1110 if let Some((i, c)) = chunk.char_indices().next_back() {
1111 let j = i + if c.is_whitespace() { 0 } else { c.len_utf8() };
1112 if j > 0 {
1113 let buf = &text[start_index..start_index + j];
1114 let local_offset = offset + start_index;
1115 let mut plan = {
1116 let font = self.fonts.as_mut().unwrap().get_mut(
1117 style.font_kind,
1118 style.font_style,
1119 style.font_weight,
1120 );
1121 font.set_size(font_size, self.dpi);
1122 font.plan(buf, None, style.font_features.as_deref())
1123 };
1124 plan.space_out(style.letter_spacing);
1125
1126 items.push(ParagraphItem::Box {
1127 width: plan.width,
1128 data: ParagraphElement::Text(TextElement {
1129 offset: local_offset,
1130 language: style.language.clone(),
1131 text: buf.to_string(),
1132 plan,
1133 font_features: style.font_features.clone(),
1134 font_kind: style.font_kind,
1135 font_style: style.font_style,
1136 font_weight: style.font_weight,
1137 vertical_align: style.vertical_align,
1138 letter_spacing: style.letter_spacing,
1139 font_size,
1140 color: style.color,
1141 uri: style.uri.clone(),
1142 }),
1143 });
1144 }
1145 if c.is_whitespace() {
1146 if c == '\n' && parent_style.retain_whitespace {
1147 let stretch =
1148 if parent_style.text_align == TextAlign::Center {
1149 big_stretch
1150 } else {
1151 line_width
1152 };
1153
1154 items.push(ParagraphItem::Penalty {
1155 penalty: INFINITE_PENALTY,
1156 width: 0,
1157 flagged: false,
1158 });
1159 items.push(ParagraphItem::Glue {
1160 width: 0,
1161 stretch,
1162 shrink: 0,
1163 });
1164
1165 items.push(ParagraphItem::Penalty {
1166 width: 0,
1167 penalty: -INFINITE_PENALTY,
1168 flagged: false,
1169 });
1170
1171 if parent_style.text_align == TextAlign::Center {
1172 items.push(ParagraphItem::Box {
1173 width: 0,
1174 data: ParagraphElement::Nothing,
1175 });
1176 items.push(ParagraphItem::Penalty {
1177 width: 0,
1178 penalty: INFINITE_PENALTY,
1179 flagged: false,
1180 });
1181 items.push(ParagraphItem::Glue {
1182 width: 0,
1183 stretch: big_stretch,
1184 shrink: 0,
1185 });
1186 }
1187 start_index += chunk.len();
1188 continue;
1189 }
1190
1191 let last_c =
1192 text[..start_index + i].chars().next_back().or_else(|| {
1193 if index > 0 {
1194 inlines[index - 1]
1195 .text()
1196 .and_then(|text| text.chars().next_back())
1197 } else {
1198 None
1199 }
1200 });
1201
1202 let has_more = text[start_index + i..]
1203 .chars()
1204 .any(|c| !c.is_xml_whitespace())
1205 || inlines[index + 1..].iter().any(|m| {
1206 m.text().map_or(false, |text| {
1207 text.chars().any(|c| !c.is_xml_whitespace())
1208 })
1209 });
1210
1211 if !parent_style.retain_whitespace
1212 && c.is_xml_whitespace()
1213 && (last_c.map(|c| c.is_xml_whitespace()) != Some(false)
1214 || !has_more)
1215 {
1216 start_index += chunk.len();
1217 continue;
1218 }
1219
1220 let mut width = if !parent_style.retain_whitespace {
1221 space_plan.glyph_advance(0)
1222 } else if let Some(index) =
1223 FONT_SPACES.chars().position(|x| x == c)
1224 {
1225 space_plan.glyph_advance(index)
1226 } else if let Some(ratio) = WORD_SPACE_RATIOS.get(&c) {
1227 (space_plan.glyph_advance(0) as f32 * ratio) as i32
1228 } else if let Some(ratio) = EM_SPACE_RATIOS.get(&c) {
1229 pt_to_px(style.font_size * ratio, self.dpi).round() as i32
1230 } else {
1231 space_plan.glyph_advance(0)
1232 };
1233
1234 width += match style.word_spacing {
1235 WordSpacing::Normal => 0,
1236 WordSpacing::Length(l) => l,
1237 WordSpacing::Ratio(r) => (r * width as f32) as i32,
1238 } + style.letter_spacing;
1239
1240 let is_unbreakable =
1241 c == '\u{00A0}' || c == '\u{202F}' || c == '\u{2007}';
1242
1243 if (is_unbreakable
1244 || (parent_style.retain_whitespace
1245 && c.is_xml_whitespace()))
1246 && (last_c == Some('\n') || last_c.is_none())
1247 {
1248 items.push(ParagraphItem::Box {
1249 width: 0,
1250 data: ParagraphElement::Nothing,
1251 });
1252 }
1253
1254 if is_unbreakable {
1255 items.push(ParagraphItem::Penalty {
1256 width: 0,
1257 penalty: INFINITE_PENALTY,
1258 flagged: false,
1259 });
1260 }
1261
1262 match parent_style.text_align {
1263 TextAlign::Justify => {
1264 items.push(ParagraphItem::Glue {
1265 width,
1266 stretch: width / 2,
1267 shrink: width / 3,
1268 });
1269 }
1270 TextAlign::Center => {
1271 if style.font_kind == FontKind::Monospace
1272 || is_unbreakable
1273 {
1274 items.push(ParagraphItem::Glue {
1275 width,
1276 stretch: 0,
1277 shrink: 0,
1278 });
1279 } else {
1280 let stretch = 3 * width;
1281 items.push(ParagraphItem::Glue {
1282 width: 0,
1283 stretch,
1284 shrink: 0,
1285 });
1286 items.push(ParagraphItem::Penalty {
1287 width: 0,
1288 penalty: 0,
1289 flagged: false,
1290 });
1291 items.push(ParagraphItem::Glue {
1292 width,
1293 stretch: -2 * stretch,
1294 shrink: 0,
1295 });
1296 items.push(ParagraphItem::Box {
1297 width: 0,
1298 data: ParagraphElement::Nothing,
1299 });
1300 items.push(ParagraphItem::Penalty {
1301 width: 0,
1302 penalty: INFINITE_PENALTY,
1303 flagged: false,
1304 });
1305 items.push(ParagraphItem::Glue {
1306 width: 0,
1307 stretch,
1308 shrink: 0,
1309 });
1310 }
1311 }
1312 TextAlign::Left | TextAlign::Right => {
1313 if style.font_kind == FontKind::Monospace
1314 || is_unbreakable
1315 {
1316 items.push(ParagraphItem::Glue {
1317 width,
1318 stretch: 0,
1319 shrink: 0,
1320 });
1321 } else {
1322 let stretch = 3 * width;
1323 items.push(ParagraphItem::Glue {
1324 width: 0,
1325 stretch,
1326 shrink: 0,
1327 });
1328 items.push(ParagraphItem::Penalty {
1329 width: 0,
1330 penalty: 0,
1331 flagged: false,
1332 });
1333 items.push(ParagraphItem::Glue {
1334 width,
1335 stretch: -stretch,
1336 shrink: 0,
1337 });
1338 }
1339 }
1340 }
1341 } else if end_index < text.len() {
1342 let penalty = if c == '-' { self.hyphen_penalty } else { 0 };
1343 let flagged = penalty > 0;
1344 if matches!(
1345 parent_style.text_align,
1346 TextAlign::Justify | TextAlign::Center
1347 ) {
1348 items.push(ParagraphItem::Penalty {
1349 width: 0,
1350 penalty,
1351 flagged,
1352 });
1353 } else {
1354 let stretch = 3 * space_plan.glyph_advance(0);
1355 items.push(ParagraphItem::Penalty {
1356 width: 0,
1357 penalty: INFINITE_PENALTY,
1358 flagged: false,
1359 });
1360 items.push(ParagraphItem::Glue {
1361 width: 0,
1362 stretch,
1363 shrink: 0,
1364 });
1365 items.push(ParagraphItem::Penalty {
1366 width: 0,
1367 penalty: 10 * penalty,
1368 flagged: true,
1369 });
1370 items.push(ParagraphItem::Glue {
1371 width: 0,
1372 stretch: -stretch,
1373 shrink: 0,
1374 });
1375 }
1376 }
1377 }
1378 start_index += chunk.len();
1379 }
1380 }
1381 }
1382 InlineMaterial::LineBreak => {
1383 let stretch = if parent_style.text_align == TextAlign::Center {
1384 big_stretch
1385 } else {
1386 line_width
1387 };
1388
1389 items.push(ParagraphItem::Penalty {
1390 penalty: INFINITE_PENALTY,
1391 width: 0,
1392 flagged: false,
1393 });
1394 items.push(ParagraphItem::Glue {
1395 width: 0,
1396 stretch,
1397 shrink: 0,
1398 });
1399
1400 items.push(ParagraphItem::Penalty {
1401 width: 0,
1402 penalty: -INFINITE_PENALTY,
1403 flagged: false,
1404 });
1405
1406 if parent_style.text_align == TextAlign::Center {
1407 items.push(ParagraphItem::Box {
1408 width: 0,
1409 data: ParagraphElement::Nothing,
1410 });
1411 items.push(ParagraphItem::Penalty {
1412 width: 0,
1413 penalty: INFINITE_PENALTY,
1414 flagged: false,
1415 });
1416 items.push(ParagraphItem::Glue {
1417 width: 0,
1418 stretch: big_stretch,
1419 shrink: 0,
1420 });
1421 }
1422 }
1423 InlineMaterial::Glue(GlueMaterial {
1424 width,
1425 stretch,
1426 shrink,
1427 }) => {
1428 items.push(ParagraphItem::Glue {
1429 width: *width,
1430 stretch: *stretch,
1431 shrink: *shrink,
1432 });
1433 }
1434 InlineMaterial::Penalty(PenaltyMaterial {
1435 width,
1436 penalty,
1437 flagged,
1438 }) => {
1439 items.push(ParagraphItem::Penalty {
1440 width: *width,
1441 penalty: *penalty,
1442 flagged: *flagged,
1443 });
1444 }
1445 InlineMaterial::Box(width) => {
1446 items.push(ParagraphItem::Box {
1447 width: *width,
1448 data: ParagraphElement::Nothing,
1449 });
1450 }
1451 }
1452 }
1453
1454 if !items.is_empty() && items.last().map(ParagraphItem::penalty) != Some(-INFINITE_PENALTY)
1455 {
1456 items.push(ParagraphItem::Penalty {
1457 penalty: INFINITE_PENALTY,
1458 width: 0,
1459 flagged: false,
1460 });
1461
1462 let stretch = if parent_style.text_align == TextAlign::Center {
1463 big_stretch
1464 } else {
1465 line_width
1466 };
1467 items.push(ParagraphItem::Glue {
1468 width: 0,
1469 stretch,
1470 shrink: 0,
1471 });
1472
1473 items.push(ParagraphItem::Penalty {
1474 penalty: -INFINITE_PENALTY,
1475 width: 0,
1476 flagged: true,
1477 });
1478 }
1479
1480 (items, floats)
1481 }
1482
1483 fn place_paragraphs(
1484 &mut self,
1485 inlines: &[InlineMaterial],
1486 style: &StyleData,
1487 root_data: &RootData,
1488 markers: &[usize],
1489 resource_fetcher: &mut dyn ResourceFetcher,
1490 draw_state: &mut DrawState,
1491 rects: &mut Vec<Option<Rectangle>>,
1492 display_list: &mut Vec<Page>,
1493 ) {
1494 let line_width = style.end_x - style.start_x;
1495 let (mut items, floats) =
1496 self.make_paragraph_items(inlines, style, line_width, resource_fetcher);
1497
1498 if items.is_empty() {
1499 return;
1500 }
1501
1502 let position = &mut draw_state.position;
1503
1504 let text_indent = if style.text_align == TextAlign::Center {
1505 0
1506 } else {
1507 style.text_indent
1508 };
1509
1510 let (ascender, descender) = {
1511 let fonts = self.fonts.as_mut().unwrap();
1512 let font = fonts.get_mut(style.font_kind, style.font_style, style.font_weight);
1513 font.set_size((style.font_size * 64.0) as u32, self.dpi);
1514 (font.ascender(), font.descender())
1515 };
1516
1517 let ratio = ascender as f32 / (ascender - descender) as f32;
1518 let space_top = (style.line_height as f32 * ratio) as i32;
1519 let space_bottom = style.line_height - space_top;
1520
1521 position.y += style.margin.top + space_top;
1522
1523 let mut page = display_list.pop().unwrap();
1524 let mut page_rect = rects.pop().unwrap();
1525 if position.y > root_data.rect.max.y - space_bottom {
1526 rects.push(page_rect.take());
1527 display_list.push(page);
1528 position.y = root_data.rect.min.y + space_top;
1529 page = Vec::new();
1530 }
1531
1532 let page_index = display_list.len();
1533
1534 for mut element in floats.into_iter() {
1535 let horiz_margin = element.margin.left + element.margin.right;
1536 let vert_margin = element.margin.top + element.margin.bottom;
1537 let mut width = element.width;
1538 let mut height = element.height;
1539
1540 let max_width = line_width / 3;
1541 if width + horiz_margin > max_width {
1542 let ratio = (max_width - horiz_margin) as f32 / width as f32;
1543 element.scale *= ratio;
1544 width = max_width - horiz_margin;
1545 height = (ratio * height as f32).round() as i32;
1546 }
1547
1548 let mut y_min = position.y - space_top;
1549 let side = if element.float == Some(Float::Left) {
1550 0
1551 } else {
1552 1
1553 };
1554
1555 if let Some(ref mut floating_rects) = draw_state.floats.get_mut(&page_index) {
1556 if let Some(orect) = floating_rects.iter().rev().find(|orect| {
1557 orect.max.y > y_min && (orect.min.x - style.start_x).signum() == side
1558 }) {
1559 y_min = orect.max.y;
1560 }
1561 }
1562
1563 let max_height = 2 * (root_data.rect.max.y - space_bottom - y_min) / 3;
1564 if height + vert_margin > max_height {
1565 let ratio = (max_height - vert_margin) as f32 / height as f32;
1566 element.scale *= ratio;
1567 height = max_height - vert_margin;
1568 width = (ratio * width as f32).round() as i32;
1569 }
1570
1571 if width > 0 && height > 0 {
1572 let mut rect = if element.float == Some(Float::Left) {
1573 rect![
1574 style.start_x,
1575 y_min,
1576 style.start_x + width + horiz_margin,
1577 y_min + height + vert_margin
1578 ]
1579 } else {
1580 rect![
1581 style.end_x - width - horiz_margin,
1582 y_min,
1583 style.end_x,
1584 y_min + height + vert_margin
1585 ]
1586 };
1587
1588 let floating_rects = draw_state.floats.entry(page_index).or_default();
1589 floating_rects.push(rect);
1590
1591 rect.shrink(&element.margin);
1592 page.push(DrawCommand::Image(ImageCommand {
1593 offset: element.offset + root_data.start_offset,
1594 position: rect.min,
1595 rect,
1596 scale: element.scale,
1597 path: element.path,
1598 uri: element.uri,
1599 }));
1600 }
1601 }
1602
1603 let para_shape = if let Some(floating_rects) = draw_state.floats.get(&page_index) {
1604 let max_lines = (root_data.rect.max.y - position.y + space_top) / style.line_height;
1605 let mut para_shape = Vec::new();
1606 for index in 0..max_lines {
1607 let y_min = position.y - space_top + index * style.line_height;
1608 let mut rect = rect![
1609 pt!(style.start_x, y_min),
1610 pt!(style.end_x, y_min + style.line_height)
1611 ];
1612 for frect in floating_rects {
1613 if rect.overlaps(frect) {
1614 if frect.min.x > rect.min.x {
1615 rect.max.x = frect.min.x;
1616 } else {
1617 rect.min.x = frect.max.x;
1618 }
1619 }
1620 }
1621 para_shape.push((rect.min.x, rect.max.x));
1622 }
1623 para_shape.push((style.start_x, style.end_x));
1624 para_shape
1625 } else {
1626 vec![(style.start_x, style.end_x); 2]
1627 };
1628
1629 let mut line_lengths: Vec<i32> = para_shape.iter().map(|(a, b)| b - a).collect();
1630 line_lengths[0] -= text_indent;
1631
1632 let mut bps = total_fit(&items, &line_lengths, self.stretch_tolerance, 0);
1633
1634 let mut hyph_indices = Vec::new();
1635 let mut glue_drifts = Vec::new();
1636
1637 if bps.is_empty() && style.text_align != TextAlign::Center {
1638 if let Some(dictionary) = hyph_lang(
1639 style
1640 .language
1641 .as_ref()
1642 .map_or(DEFAULT_HYPH_LANG, String::as_str),
1643 )
1644 .and_then(|lang| HYPHENATION_PATTERNS.get(&lang))
1645 {
1646 items = self.hyphenate_paragraph(style, dictionary, items, &mut hyph_indices);
1647 bps = total_fit(&items, &line_lengths, self.stretch_tolerance, 0);
1648 }
1649 }
1650
1651 if bps.is_empty() {
1652 bps = standard_fit(&items, &line_lengths, self.stretch_tolerance);
1653 }
1654
1655 if bps.is_empty() {
1656 let max_width = *line_lengths.iter().min().unwrap();
1657
1658 for itm in &mut items {
1659 if let ParagraphItem::Box { width, data } = itm {
1660 if *width > max_width {
1661 match data {
1662 ParagraphElement::Text(TextElement {
1663 plan,
1664 font_kind,
1665 font_style,
1666 font_weight,
1667 font_size,
1668 ..
1669 }) => {
1670 let font = self.fonts.as_mut().unwrap().get_mut(
1671 *font_kind,
1672 *font_style,
1673 *font_weight,
1674 );
1675 font.set_size(*font_size, self.dpi);
1676 font.crop_right(plan, max_width);
1677 *width = plan.width;
1678 }
1679 ParagraphElement::Image(ImageElement {
1680 width: image_width,
1681 height,
1682 scale,
1683 ..
1684 }) => {
1685 let ratio = max_width as f32 / *image_width as f32;
1686 *scale *= ratio;
1687 *image_width = max_width;
1688 *height = (*height as f32 * ratio) as i32;
1689 *width = max_width;
1690 }
1691 _ => (),
1692 }
1693 }
1694 }
1695 }
1696
1697 bps = standard_fit(&items, &line_lengths, self.stretch_tolerance);
1698 }
1699
1700 if !bps.is_empty() && !hyph_indices.is_empty() {
1702 items = self.cleanup_paragraph(items, &hyph_indices, &mut glue_drifts, &mut bps);
1703 }
1704
1705 let mut last_index = 0;
1706 let mut markers_index = 0;
1707 let mut last_x_position = 0;
1708 let mut is_first_line = true;
1709
1710 if let Some(prefix) = draw_state.prefix.as_ref() {
1711 let font_size = (style.font_size * 64.0) as u32;
1712 let prefix_plan = {
1713 let font = self.fonts.as_mut().unwrap().get_mut(
1714 style.font_kind,
1715 style.font_style,
1716 style.font_weight,
1717 );
1718 font.set_size(font_size, self.dpi);
1719 font.plan(prefix, None, style.font_features.as_deref())
1720 };
1721 let (start_x, _) = para_shape[0];
1722 let pt = pt!(start_x - prefix_plan.width, position.y);
1723 let rect = rect![
1724 pt + pt!(0, -ascender),
1725 pt + pt!(prefix_plan.width, -descender)
1726 ];
1727 if let Some(first_offset) = inlines.iter().filter_map(|elt| elt.offset()).next() {
1728 page.push(DrawCommand::ExtraText(TextCommand {
1729 offset: root_data.start_offset + first_offset,
1730 position: pt,
1731 rect,
1732 text: prefix.to_string(),
1733 plan: prefix_plan,
1734 uri: None,
1735 font_kind: style.font_kind,
1736 font_style: style.font_style,
1737 font_weight: style.font_weight,
1738 font_size,
1739 color: style.color,
1740 }));
1741 }
1742 }
1743
1744 for (j, bp) in bps.into_iter().enumerate() {
1745 let drift = if glue_drifts.is_empty() {
1746 0.0
1747 } else {
1748 glue_drifts[j]
1749 };
1750
1751 let (start_x, end_x) = para_shape[j.min(para_shape.len() - 1)];
1752
1753 let Breakpoint {
1754 index,
1755 width,
1756 mut ratio,
1757 } = bp;
1758 let mut epsilon: f32 = 0.0;
1759 let current_text_indent = if is_first_line { text_indent } else { 0 };
1760
1761 match style.text_align {
1762 TextAlign::Right => position.x = end_x - width - current_text_indent,
1763 _ => position.x = start_x + current_text_indent,
1764 }
1765
1766 if style.text_align == TextAlign::Left || style.text_align == TextAlign::Right {
1767 ratio = ratio.min(0.0);
1768 }
1769
1770 while last_index < index && !items[last_index].is_box() {
1771 last_index += 1;
1772 }
1773
1774 let start_command_index = page.len();
1775
1776 for i in last_index..index {
1777 match items[i] {
1778 ParagraphItem::Box { ref data, width } => {
1779 match data {
1780 ParagraphElement::Text(element) => {
1781 let pt = pt!(position.x, position.y - element.vertical_align);
1782 let rect = rect![
1783 pt + pt!(0, -ascender),
1784 pt + pt!(element.plan.width, -descender)
1785 ];
1786 if let Some(pr) = page_rect.as_mut() {
1787 pr.absorb(&rect);
1788 } else {
1789 page_rect = Some(rect);
1790 }
1791 while let Some(offset) = markers.get(markers_index) {
1792 if *offset < element.offset {
1793 page.push(DrawCommand::Marker(
1794 root_data.start_offset + *offset,
1795 ));
1796 markers_index += 1;
1797 } else {
1798 break;
1799 }
1800 }
1801 page.push(DrawCommand::Text(TextCommand {
1802 offset: element.offset + root_data.start_offset,
1803 position: pt,
1804 rect,
1805 text: element.text.clone(),
1806 plan: element.plan.clone(),
1807 uri: element.uri.clone(),
1808 font_kind: element.font_kind,
1809 font_style: element.font_style,
1810 font_weight: element.font_weight,
1811 font_size: element.font_size,
1812 color: element.color,
1813 }));
1814 }
1815 ParagraphElement::Image(element) => {
1816 while let Some(offset) = markers.get(markers_index) {
1817 if *offset < element.offset {
1818 page.push(DrawCommand::Marker(
1819 root_data.start_offset + *offset,
1820 ));
1821 markers_index += 1;
1822 } else {
1823 break;
1824 }
1825 }
1826 let mut k = last_index;
1827 while k < index {
1828 match items[k] {
1829 ParagraphItem::Box { width, .. } if width > 0 && k != i => {
1830 break;
1831 }
1832 _ => k += 1,
1833 }
1834 }
1835 let (w, h, pt, scale) = if k == index {
1837 position.y += element.margin.top;
1838 if element.display == Display::Block {
1839 position.y -= space_top;
1840 }
1841 let (mut width, mut height) = (element.width, element.height);
1842 let r = width as f32 / height as f32;
1843 if position.y + height > root_data.rect.max.y - space_bottom {
1844 let mut ratio =
1845 (root_data.rect.max.y - position.y - space_bottom)
1846 as f32
1847 / height as f32;
1848 if ratio < 0.33 {
1849 display_list.push(page);
1850 position.y = root_data.rect.min.y;
1851 page = Vec::new();
1852 ratio =
1853 ((root_data.rect.max.y - position.y - space_bottom)
1854 as f32
1855 / height as f32)
1856 .min(1.0);
1857 }
1858 height = (height as f32 * ratio).round() as i32;
1859 width = (height as f32 * r).round() as i32;
1860 }
1861 let scale = element.scale * width as f32 / element.width as f32;
1862 if element.display == Display::Block {
1863 let mut left_margin = element.margin.left;
1864 let total_width =
1865 left_margin + width + element.margin.right;
1866 if total_width > line_width {
1867 let remaining_space = line_width - width;
1868 let ratio = left_margin as f32
1869 / (left_margin + element.margin.right) as f32;
1870 left_margin =
1871 (ratio * remaining_space as f32).round() as i32;
1872 }
1873 position.x = start_x + left_margin;
1874 if last_x_position < position.x
1875 && position.y > root_data.rect.min.y
1876 {
1877 position.y -= style.line_height;
1878 }
1879 } else if width < element.width {
1880 if style.text_align == TextAlign::Center {
1881 position.x += (element.width - width) / 2;
1882 } else if style.text_align == TextAlign::Right {
1883 position.x += element.width - width;
1884 }
1885 }
1886 let pt = pt!(position.x, position.y);
1887 position.y += height + element.margin.bottom;
1888 if element.display == Display::Block {
1889 position.y -= space_bottom;
1890 }
1891 (width, height, pt, scale)
1892 } else {
1893 let mut pt = pt!(
1894 position.x,
1895 position.y - element.height - element.vertical_align
1896 );
1897
1898 let y_min = position.y - ascender;
1899 let delta = y_min - pt.y;
1900 if delta > 0 {
1901 pt.y += delta;
1902 let y_max = root_data.rect.max.y - space_bottom;
1903 if pt.y + element.height > y_max {
1904 let mut start_commands = page
1905 .drain(start_command_index..)
1906 .collect::<Vec<DrawCommand>>();
1907 display_list.push(page);
1908 let next_baseline = (root_data.rect.min.y + space_top
1909 - ascender
1910 + element.height)
1911 .min(y_max);
1912 for dc in &mut start_commands {
1913 if let Some(pt) = dc.position_mut() {
1914 pt.y += next_baseline - position.y;
1915 }
1916 }
1917 pt.y = next_baseline
1918 - element.height
1919 - element.vertical_align;
1920 position.y = next_baseline;
1921 page = start_commands;
1922 } else {
1923 for dc in &mut page[start_command_index..] {
1924 if let Some(pt) = dc.position_mut() {
1925 pt.y += delta;
1926 }
1927 }
1928 position.y += delta;
1929 }
1930 }
1931
1932 (element.width, element.height, pt, element.scale)
1933 };
1934
1935 let rect = rect![pt, pt + pt!(w, h)];
1936
1937 if let Some(pr) = page_rect.as_mut() {
1938 pr.absorb(&rect);
1939 } else {
1940 page_rect = Some(rect);
1941 }
1942
1943 page.push(DrawCommand::Image(ImageCommand {
1944 offset: element.offset + root_data.start_offset,
1945 position: pt,
1946 rect,
1947 scale,
1948 path: element.path.clone(),
1949 uri: element.uri.clone(),
1950 }));
1951 }
1952 _ => (),
1953 }
1954
1955 position.x += width;
1956 last_x_position = position.x;
1957 }
1958 ParagraphItem::Glue {
1959 width,
1960 stretch,
1961 shrink,
1962 } if ratio.is_finite() => {
1963 let amplitude = if ratio.is_sign_positive() {
1964 stretch
1965 } else {
1966 shrink
1967 };
1968 let exact_width = width as f32 + ratio * amplitude as f32 + drift;
1969 let approx_width = if epsilon.is_sign_positive() {
1970 exact_width.floor() as i32
1971 } else {
1972 exact_width.ceil() as i32
1973 };
1974 if stretch == 0 && shrink == 0 {
1976 let rect = rect![
1977 *position + pt!(0, -ascender),
1978 *position + pt!(approx_width, -descender)
1979 ];
1980 if let Some(pr) = page_rect.as_mut() {
1981 pr.absorb(&rect);
1982 } else {
1983 page_rect = Some(rect);
1984 }
1985 }
1986 epsilon += approx_width as f32 - exact_width;
1987 position.x += approx_width;
1988 }
1989 _ => (),
1990 }
1991 }
1992
1993 if let ParagraphItem::Penalty { width, .. } = items[index] {
1994 if width > 0 {
1995 if let Some(DrawCommand::Text(tc)) = page.last_mut() {
1996 let font = self.fonts.as_mut().unwrap().get_mut(
1997 tc.font_kind,
1998 tc.font_style,
1999 tc.font_weight,
2000 );
2001 font.set_size(tc.font_size, self.dpi);
2002 let mut hyphen_plan = font.plan("-", None, None);
2003 tc.rect.max.x += hyphen_plan.width;
2004 tc.plan.append(&mut hyphen_plan);
2005 tc.text.push('\u{00AD}');
2006 }
2007 }
2008 }
2009
2010 last_index = index;
2011 is_first_line = false;
2012
2013 if index < items.len() - 1 {
2014 position.y += style.line_height;
2015 }
2016
2017 if position.y > root_data.rect.max.y - space_bottom {
2018 rects.push(page_rect.take());
2019 display_list.push(page);
2020 position.y = root_data.rect.min.y + space_top;
2021 page = Vec::new();
2022 }
2023 }
2024
2025 let last_page = if !page.is_empty() {
2026 Some(&mut page)
2027 } else {
2028 display_list.iter_mut().rev().find(|page| !page.is_empty())
2029 };
2030
2031 if let Some(last_page) = last_page {
2032 while let Some(offset) = markers.get(markers_index) {
2033 last_page.push(DrawCommand::Marker(root_data.start_offset + *offset));
2034 markers_index += 1;
2035 }
2036 }
2037
2038 rects.push(page_rect.take());
2039
2040 position.y += space_bottom;
2041
2042 display_list.push(page);
2043 }
2044
2045 #[inline]
2046 fn box_from_chunk(
2047 &mut self,
2048 chunk: &str,
2049 index: usize,
2050 element: &TextElement,
2051 ) -> ParagraphItem<ParagraphElement> {
2052 let offset = element.offset + index;
2053 let mut plan = {
2054 let font = self.fonts.as_mut().unwrap().get_mut(
2055 element.font_kind,
2056 element.font_style,
2057 element.font_weight,
2058 );
2059 font.set_size(element.font_size, self.dpi);
2060 font.plan(chunk, None, element.font_features.as_deref())
2061 };
2062 plan.space_out(element.letter_spacing);
2063 ParagraphItem::Box {
2064 width: plan.width,
2065 data: ParagraphElement::Text(TextElement {
2066 offset,
2067 text: chunk.to_string(),
2068 plan,
2069 language: element.language.clone(),
2070 font_features: element.font_features.clone(),
2071 font_kind: element.font_kind,
2072 font_style: element.font_style,
2073 font_weight: element.font_weight,
2074 font_size: element.font_size,
2075 vertical_align: element.vertical_align,
2076 letter_spacing: element.letter_spacing,
2077 color: element.color,
2078 uri: element.uri.clone(),
2079 }),
2080 }
2081 }
2082
2083 fn hyphenate_paragraph(
2084 &mut self,
2085 style: &StyleData,
2086 dictionary: &Standard,
2087 items: Vec<ParagraphItem<ParagraphElement>>,
2088 hyph_indices: &mut Vec<[usize; 2]>,
2089 ) -> Vec<ParagraphItem<ParagraphElement>> {
2090 let mut hyph_items = Vec::with_capacity(items.len());
2091
2092 for itm in items {
2093 match itm {
2094 ParagraphItem::Box {
2095 data: ParagraphElement::Text(ref element),
2096 ..
2097 } => {
2098 let text = &element.text;
2099 let (hyphen_width, stretch) = {
2100 let font = self.fonts.as_mut().unwrap().get_mut(
2101 element.font_kind,
2102 element.font_style,
2103 element.font_weight,
2104 );
2105 font.set_size(element.font_size, self.dpi);
2106 let plan = font.plan(" -", None, element.font_features.as_deref());
2107 (plan.glyph_advance(1), 3 * plan.glyph_advance(0))
2108 };
2109
2110 let mut index_before =
2111 text.find(char::is_alphabetic).unwrap_or_else(|| text.len());
2112 if index_before > 0 {
2113 let subelem = self.box_from_chunk(&text[0..index_before], 0, element);
2114 hyph_items.push(subelem);
2115 }
2116
2117 let mut index_after = text[index_before..]
2118 .find(|c: char| !c.is_alphabetic())
2119 .map(|i| index_before + i)
2120 .unwrap_or_else(|| text.len());
2121 while index_before < index_after {
2122 let mut index = 0;
2123 let chunk = &text[index_before..index_after];
2124 let len_before = hyph_items.len();
2125
2126 for segment in dictionary.hyphenate(chunk).iter().segments() {
2127 let subelem =
2128 self.box_from_chunk(segment, index_before + index, element);
2129 hyph_items.push(subelem);
2130 index += segment.len();
2131 if index < chunk.len() {
2132 if style.text_align == TextAlign::Justify {
2133 hyph_items.push(ParagraphItem::Penalty {
2134 width: hyphen_width,
2135 penalty: self.hyphen_penalty,
2136 flagged: true,
2137 });
2138 } else {
2139 hyph_items.push(ParagraphItem::Penalty {
2140 width: 0,
2141 penalty: INFINITE_PENALTY,
2142 flagged: false,
2143 });
2144 hyph_items.push(ParagraphItem::Glue {
2145 width: 0,
2146 stretch,
2147 shrink: 0,
2148 });
2149 hyph_items.push(ParagraphItem::Penalty {
2150 width: hyphen_width,
2151 penalty: 10 * self.hyphen_penalty,
2152 flagged: true,
2153 });
2154 hyph_items.push(ParagraphItem::Glue {
2155 width: 0,
2156 stretch: -stretch,
2157 shrink: 0,
2158 });
2159 }
2160 }
2161 }
2162
2163 let len_after = hyph_items.len();
2164 if len_after > 1 + len_before {
2165 hyph_indices.push([len_before, len_after]);
2166 }
2167 index_before = text[index_after..]
2168 .find(char::is_alphabetic)
2169 .map(|i| index_after + i)
2170 .unwrap_or_else(|| text.len());
2171 if index_before > index_after {
2172 let subelem = self.box_from_chunk(
2173 &text[index_after..index_before],
2174 index_after,
2175 &element,
2176 );
2177 hyph_items.push(subelem);
2178 }
2179
2180 index_after = text[index_before..]
2181 .find(|c: char| !c.is_alphabetic())
2182 .map(|i| index_before + i)
2183 .unwrap_or_else(|| text.len());
2184 }
2185 }
2186 _ => hyph_items.push(itm),
2187 }
2188 }
2189
2190 hyph_items
2191 }
2192
2193 fn cleanup_paragraph(
2194 &mut self,
2195 items: Vec<ParagraphItem<ParagraphElement>>,
2196 hyph_indices: &[[usize; 2]],
2197 glue_drifts: &mut Vec<f32>,
2198 bps: &mut Vec<Breakpoint>,
2199 ) -> Vec<ParagraphItem<ParagraphElement>> {
2200 let mut merged_items = Vec::with_capacity(items.len());
2201 let mut j = 0;
2202 let mut k = 0;
2203 let mut index_drift = 0;
2204 let [mut start_index, mut end_index] = hyph_indices[j];
2205 let mut bp = bps[k];
2206 let mut line_stats = LineStats::default();
2207 let mut merged_element = ParagraphElement::Nothing;
2208
2209 for (i, itm) in items.into_iter().enumerate() {
2210 if i == bp.index {
2211 let mut merged_width = 0;
2212
2213 if let ParagraphElement::Text(TextElement {
2214 ref text,
2215 ref mut plan,
2216 font_size,
2217 font_kind,
2218 font_style,
2219 font_weight,
2220 letter_spacing,
2221 ref font_features,
2222 ..
2223 }) = merged_element
2224 {
2225 *plan = {
2226 let font = self.fonts.as_mut().unwrap().get_mut(
2227 font_kind,
2228 font_style,
2229 font_weight,
2230 );
2231 font.set_size(font_size, self.dpi);
2232 font.plan(text, None, font_features.as_ref().map(Vec::as_slice))
2233 };
2234 plan.space_out(letter_spacing);
2235 merged_width = plan.width;
2236 }
2237
2238 if merged_width > 0 {
2239 merged_items.push(ParagraphItem::Box {
2240 width: merged_width,
2241 data: merged_element,
2242 });
2243 merged_element = ParagraphElement::Nothing;
2244 }
2245
2246 line_stats.merged_width += merged_width;
2247 let delta_width = line_stats.merged_width - line_stats.width;
2248 glue_drifts.push(-delta_width as f32 / line_stats.glues_count as f32);
2249
2250 bps[k].index = bps[k].index.saturating_sub(index_drift);
2251 bps[k].width += delta_width;
2252 k += 1;
2253
2254 if k < bps.len() {
2255 bp = bps[k];
2256 }
2257
2258 line_stats = LineStats::default();
2259 merged_items.push(itm);
2260 if i >= start_index && i < end_index {
2261 start_index = i + 1;
2262 }
2263 } else if i >= start_index && i < end_index {
2264 if i > start_index {
2265 index_drift += 1;
2266 }
2267 if let ParagraphItem::Box { width, data } = itm {
2268 match merged_element {
2269 ParagraphElement::Text(TextElement { ref mut text, .. }) => {
2270 if let ParagraphElement::Text(TextElement {
2271 text: other_text, ..
2272 }) = data
2273 {
2274 text.push_str(&other_text);
2275 }
2276 }
2277 ParagraphElement::Nothing => merged_element = data,
2278 _ => (),
2279 }
2280 line_stats.width += width;
2281 if !line_stats.started {
2282 line_stats.started = true;
2283 }
2284 }
2285 if i == end_index - 1 {
2286 j += 1;
2287 if let Some(&[s, e]) = hyph_indices.get(j) {
2288 start_index = s;
2289 end_index = e;
2290 } else {
2291 start_index = usize::MAX;
2292 end_index = 0;
2293 }
2294 let mut merged_width = 0;
2295 if let ParagraphElement::Text(TextElement {
2296 ref text,
2297 ref mut plan,
2298 font_size,
2299 font_kind,
2300 font_style,
2301 font_weight,
2302 letter_spacing,
2303 ref font_features,
2304 ..
2305 }) = merged_element
2306 {
2307 *plan = {
2308 let font = self.fonts.as_mut().unwrap().get_mut(
2309 font_kind,
2310 font_style,
2311 font_weight,
2312 );
2313 font.set_size(font_size, self.dpi);
2314 font.plan(text, None, font_features.as_ref().map(Vec::as_slice))
2315 };
2316 plan.space_out(letter_spacing);
2317 merged_width = plan.width;
2318 }
2319 merged_items.push(ParagraphItem::Box {
2320 width: merged_width,
2321 data: merged_element,
2322 });
2323 merged_element = ParagraphElement::Nothing;
2324 line_stats.merged_width += merged_width;
2325 }
2326 } else {
2327 match itm {
2328 ParagraphItem::Glue { .. } if line_stats.started => line_stats.glues_count += 1,
2329 ParagraphItem::Box { .. } if !line_stats.started => line_stats.started = true,
2330 _ => (),
2331 }
2332 merged_items.push(itm);
2333 }
2334 }
2335
2336 merged_items
2337 }
2338
2339 pub fn render_page(
2340 &mut self,
2341 page: &[DrawCommand],
2342 scale_factor: f32,
2343 samples: usize,
2344 resource_fetcher: &mut dyn ResourceFetcher,
2345 ) -> Option<Pixmap> {
2346 let width = (self.dims.0 as f32 * scale_factor) as u32;
2347 let height = (self.dims.1 as f32 * scale_factor) as u32;
2348 let mut fb = Pixmap::try_new(width, height, samples)?;
2349
2350 for dc in page {
2351 match dc {
2352 DrawCommand::Text(TextCommand {
2353 position,
2354 plan,
2355 font_kind,
2356 font_style,
2357 font_weight,
2358 font_size,
2359 color,
2360 ..
2361 })
2362 | DrawCommand::ExtraText(TextCommand {
2363 position,
2364 plan,
2365 font_kind,
2366 font_style,
2367 font_weight,
2368 font_size,
2369 color,
2370 ..
2371 }) => {
2372 let font =
2373 self.fonts
2374 .as_mut()
2375 .unwrap()
2376 .get_mut(*font_kind, *font_style, *font_weight);
2377 let font_size = (scale_factor * *font_size as f32) as u32;
2378 let position = Point::from(scale_factor * Vec2::from(*position));
2379 let plan = plan.scale(scale_factor);
2380 font.set_size(font_size, self.dpi);
2381 font.render(&mut fb, *color, &plan, position);
2382 }
2383 DrawCommand::Image(ImageCommand {
2384 position,
2385 path,
2386 scale,
2387 ..
2388 }) => {
2389 if let Ok(buf) = resource_fetcher.fetch(path) {
2390 if let Some((pixmap, _)) = PdfOpener::new()
2391 .and_then(|opener| opener.open_memory(path, &buf))
2392 .and_then(|mut doc| {
2393 doc.pixmap(Location::Exact(0), scale_factor * *scale, samples)
2394 })
2395 {
2396 let position = Point::from(scale_factor * Vec2::from(*position));
2397 fb.draw_pixmap(&pixmap, position);
2398 }
2399 }
2400 }
2401 _ => (),
2402 }
2403 }
2404
2405 Some(fb)
2406 }
2407}
2408
2409fn format_list_prefix(kind: ListStyleType, index: usize) -> Option<String> {
2410 match kind {
2411 ListStyleType::None => None,
2412 ListStyleType::Disc => Some("• ".to_string()),
2413 ListStyleType::Circle => Some("◦ ".to_string()),
2414 ListStyleType::Square => Some("▪ ".to_string()),
2415 ListStyleType::Decimal => Some(format!("{}. ", index + 1)),
2416 ListStyleType::LowerRoman => Some(format!(
2417 "{}. ",
2418 Roman::from_unchecked(index as u32 + 1).to_lowercase()
2419 )),
2420 ListStyleType::UpperRoman => Some(format!(
2421 "{}. ",
2422 Roman::from_unchecked(index as u32 + 1).to_uppercase()
2423 )),
2424 ListStyleType::LowerAlpha | ListStyleType::UpperAlpha => {
2425 let i = index as u32 % 26;
2426 let start = if kind == ListStyleType::LowerAlpha {
2427 0x61
2428 } else {
2429 0x41
2430 };
2431 Some(format!("{}. ", char::try_from(start + i).unwrap()))
2432 }
2433 ListStyleType::LowerGreek | ListStyleType::UpperGreek => {
2434 let mut i = index as u32 % 24;
2435 if i >= 17 {
2437 i += 1;
2438 }
2439 let start = if kind == ListStyleType::LowerGreek {
2440 0x03B1
2441 } else {
2442 0x0391
2443 };
2444 Some(format!("{}. ", char::try_from(start + i).unwrap()))
2445 }
2446 }
2447}
2448
2449fn build_fonts(root_dir: Option<std::path::PathBuf>) -> Result<Fonts, Error> {
2450 let opener = FontOpener::new()?;
2451 let font_path = |name: &str| -> std::path::PathBuf {
2452 match &root_dir {
2453 Some(root) => root.join("fonts").join(name),
2454 None => CURRENT_DEVICE.install_path("fonts").join(name),
2455 }
2456 };
2457 let mut fonts = Fonts {
2458 serif: FontFamily {
2459 regular: opener.open(font_path("LibertinusSerif-Regular.otf").as_path())?,
2460 italic: opener.open(font_path("LibertinusSerif-Italic.otf").as_path())?,
2461 bold: opener.open(font_path("LibertinusSerif-Bold.otf").as_path())?,
2462 bold_italic: opener.open(font_path("LibertinusSerif-BoldItalic.otf").as_path())?,
2463 },
2464 sans_serif: FontFamily {
2465 regular: opener.open(font_path("NotoSans-Regular.ttf").as_path())?,
2466 italic: opener.open(font_path("NotoSans-Italic.ttf").as_path())?,
2467 bold: opener.open(font_path("NotoSans-Bold.ttf").as_path())?,
2468 bold_italic: opener.open(font_path("NotoSans-BoldItalic.ttf").as_path())?,
2469 },
2470 monospace: FontFamily {
2471 regular: opener.open(font_path("SourceCodeVariable-Roman.otf").as_path())?,
2472 italic: opener.open(font_path("SourceCodeVariable-Italic.otf").as_path())?,
2473 bold: opener.open(font_path("SourceCodeVariable-Roman.otf").as_path())?,
2474 bold_italic: opener.open(font_path("SourceCodeVariable-Italic.otf").as_path())?,
2475 },
2476 cursive: opener.open(font_path("Parisienne-Regular.ttf").as_path())?,
2477 fantasy: opener.open(font_path("Delius-Regular.ttf").as_path())?,
2478 };
2479 fonts.monospace.bold.set_variations(&["wght=600"]);
2480 fonts.monospace.bold_italic.set_variations(&["wght=600"]);
2481 Ok(fonts)
2482}