Skip to main content

cadmus_core/document/html/
engine.rs

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