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