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