Skip to main content

cadmus_core/document/html/
engine.rs

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