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