1use crate::color::BLACK;
2use crate::color::Color;
3use crate::device::CURRENT_DEVICE;
4use crate::font::{Font, FontFamily, RenderPlan};
5use crate::geom::{Edge, Point, Rectangle};
6pub use crate::metadata::TextAlign;
7use fxhash::FxHashMap;
8use kl_hyphenate::{Language, Load, Standard};
9use lazy_static::lazy_static;
10use std::fmt::Debug;
11use std::fs;
12use std::path::PathBuf;
13
14pub const DEFAULT_HYPH_LANG: &str = "en";
15
16#[derive(Debug, Clone)]
17pub struct RootData {
18 pub start_offset: usize,
19 pub spine_dir: PathBuf,
20 pub rect: Rectangle,
21}
22
23#[derive(Debug, Clone)]
24pub struct DrawState {
25 pub position: Point,
26 pub floats: FxHashMap<usize, Vec<Rectangle>>,
27 pub prefix: Option<String>,
28 pub min_column_widths: Vec<i32>,
29 pub max_column_widths: Vec<i32>,
30 pub column_widths: Vec<i32>,
31 pub center_table: bool,
32}
33
34impl Default for DrawState {
35 fn default() -> Self {
36 DrawState {
37 position: Point::default(),
38 floats: FxHashMap::default(),
39 prefix: None,
40 min_column_widths: Vec::new(),
41 max_column_widths: Vec::new(),
42 column_widths: Vec::new(),
43 center_table: false,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
49pub struct StyleData {
50 pub display: Display,
51 pub float: Option<Float>,
52 pub width: i32,
53 pub height: i32,
54 pub margin: Edge,
55 pub padding: Edge,
56 pub start_x: i32,
57 pub end_x: i32,
58 pub retain_whitespace: bool,
59 pub text_align: TextAlign,
60 pub text_indent: i32,
61 pub line_height: i32,
62 pub language: Option<String>,
63 pub font_kind: FontKind,
64 pub font_style: FontStyle,
65 pub font_weight: FontWeight,
66 pub font_size: f32,
67 pub font_features: Option<Vec<String>>,
68 pub color: Color,
69 pub letter_spacing: i32,
70 pub word_spacing: WordSpacing,
71 pub vertical_align: i32,
72 pub list_style_type: Option<ListStyleType>,
73 pub uri: Option<String>,
74}
75
76#[derive(Debug, Copy, Clone)]
77pub enum WordSpacing {
78 Normal,
79 Length(i32),
80 Ratio(f32),
81}
82
83#[derive(Debug, Copy, Clone, Eq, PartialEq)]
84pub enum Float {
85 Left,
86 Right,
87}
88
89#[derive(Debug, Copy, Clone, Eq, PartialEq)]
90pub enum Display {
91 Block,
92 Inline,
93 InlineTable,
94 None,
95}
96
97#[derive(Debug, Copy, Clone, Eq, PartialEq)]
98pub enum ListStyleType {
99 Disc,
100 Circle,
101 Square,
102 Decimal,
103 LowerRoman,
104 UpperRoman,
105 LowerAlpha,
106 UpperAlpha,
107 LowerGreek,
108 UpperGreek,
109 None,
110}
111
112#[derive(Debug, Clone)]
113pub struct ChildArtifact {
114 pub sibling_style: SiblingStyle,
115 pub rects: Vec<Option<Rectangle>>,
116}
117
118#[derive(Debug, Clone)]
119pub struct SiblingStyle {
120 pub padding: Edge,
121 pub margin: Edge,
122}
123
124#[derive(Debug, Clone)]
125pub struct LineStats {
126 pub width: i32,
127 pub merged_width: i32,
128 pub glues_count: usize,
129 pub started: bool,
130}
131
132impl Default for LineStats {
133 fn default() -> Self {
134 LineStats {
135 width: 0,
136 merged_width: 0,
137 glues_count: 0,
138 started: false,
139 }
140 }
141}
142
143impl Default for SiblingStyle {
144 fn default() -> Self {
145 SiblingStyle {
146 padding: Edge::default(),
147 margin: Edge::default(),
148 }
149 }
150}
151
152#[derive(Debug, Clone)]
153pub struct LoopContext {
154 pub index: usize,
155 pub sibling_style: SiblingStyle,
156 pub is_first: bool,
157 pub is_last: bool,
158}
159
160impl Default for LoopContext {
161 fn default() -> Self {
162 LoopContext {
163 index: 0,
164 sibling_style: SiblingStyle::default(),
165 is_first: false,
166 is_last: false,
167 }
168 }
169}
170
171impl Default for StyleData {
172 fn default() -> Self {
173 StyleData {
174 display: Display::Block,
175 float: None,
176 width: 0,
177 height: 0,
178 margin: Edge::default(),
179 padding: Edge::default(),
180 start_x: 0,
181 end_x: 0,
182 retain_whitespace: false,
183 text_align: TextAlign::Left,
184 text_indent: 0,
185 line_height: 0,
186 language: None,
187 font_kind: FontKind::Serif,
188 font_style: FontStyle::Normal,
189 font_weight: FontWeight::Normal,
190 font_size: 0.0,
191 font_features: None,
192 color: BLACK,
193 letter_spacing: 0,
194 word_spacing: WordSpacing::Normal,
195 vertical_align: 0,
196 list_style_type: None,
197 uri: None,
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
203pub enum InlineMaterial {
204 Text(TextMaterial),
205 Image(ImageMaterial),
206 Glue(GlueMaterial),
207 Penalty(PenaltyMaterial),
208 Box(i32),
209 LineBreak,
210}
211
212impl InlineMaterial {
213 pub fn offset(&self) -> Option<usize> {
214 match self {
215 InlineMaterial::Text(TextMaterial { offset, .. })
216 | InlineMaterial::Image(ImageMaterial { offset, .. }) => Some(*offset),
217 _ => None,
218 }
219 }
220
221 pub fn text(&self) -> Option<&str> {
222 match self {
223 InlineMaterial::Text(TextMaterial { text, .. }) => Some(text),
224 _ => None,
225 }
226 }
227}
228
229#[derive(Debug, Clone)]
230pub struct TextMaterial {
231 pub offset: usize,
232 pub text: String,
233 pub style: StyleData,
234}
235
236#[derive(Debug, Clone)]
237pub struct ImageMaterial {
238 pub offset: usize,
239 pub path: String,
240 pub style: StyleData,
241}
242
243#[derive(Debug, Clone)]
244pub struct GlueMaterial {
245 pub width: i32,
246 pub stretch: i32,
247 pub shrink: i32,
248}
249
250#[derive(Debug, Clone)]
251pub struct PenaltyMaterial {
252 pub width: i32,
253 pub penalty: i32,
254 pub flagged: bool,
255}
256
257#[derive(Debug, Copy, Clone, Eq, PartialEq)]
258pub enum FontKind {
259 Serif,
260 SansSerif,
261 Monospace,
262 Cursive,
263 Fantasy,
264}
265
266#[derive(Debug, Copy, Clone)]
267pub enum FontStyle {
268 Normal,
269 Italic,
270}
271
272#[derive(Debug, Copy, Clone)]
273pub enum FontWeight {
274 Normal,
275 Bold,
276}
277
278pub struct Fonts {
279 pub serif: FontFamily,
280 pub sans_serif: FontFamily,
281 pub monospace: FontFamily,
282 pub cursive: Font,
283 pub fantasy: Font,
284}
285
286impl Fonts {
287 pub fn get_mut(
288 &mut self,
289 font_kind: FontKind,
290 font_style: FontStyle,
291 font_weight: FontWeight,
292 ) -> &mut Font {
293 match font_kind {
294 FontKind::Serif => match (font_style, font_weight) {
295 (FontStyle::Normal, FontWeight::Normal) => &mut self.serif.regular,
296 (FontStyle::Normal, FontWeight::Bold) => &mut self.serif.bold,
297 (FontStyle::Italic, FontWeight::Normal) => &mut self.serif.italic,
298 (FontStyle::Italic, FontWeight::Bold) => &mut self.serif.bold_italic,
299 },
300 FontKind::SansSerif => match (font_style, font_weight) {
301 (FontStyle::Normal, FontWeight::Normal) => &mut self.sans_serif.regular,
302 (FontStyle::Normal, FontWeight::Bold) => &mut self.sans_serif.bold,
303 (FontStyle::Italic, FontWeight::Normal) => &mut self.sans_serif.italic,
304 (FontStyle::Italic, FontWeight::Bold) => &mut self.sans_serif.bold_italic,
305 },
306 FontKind::Monospace => match (font_style, font_weight) {
307 (FontStyle::Normal, FontWeight::Normal) => &mut self.monospace.regular,
308 (FontStyle::Normal, FontWeight::Bold) => &mut self.monospace.bold,
309 (FontStyle::Italic, FontWeight::Normal) => &mut self.monospace.italic,
310 (FontStyle::Italic, FontWeight::Bold) => &mut self.monospace.bold_italic,
311 },
312 FontKind::Cursive => &mut self.cursive,
313 FontKind::Fantasy => &mut self.fantasy,
314 }
315 }
316}
317
318#[derive(Debug, Clone)]
319pub enum ParagraphElement {
320 Text(TextElement),
321 Image(ImageElement),
322 Nothing,
323}
324
325#[derive(Debug, Clone)]
326pub struct TextElement {
327 pub offset: usize,
328 pub language: Option<String>,
329 pub text: String,
330 pub plan: RenderPlan,
331 pub font_features: Option<Vec<String>>,
332 pub font_kind: FontKind,
333 pub font_style: FontStyle,
334 pub font_weight: FontWeight,
335 pub font_size: u32,
336 pub letter_spacing: i32,
337 pub vertical_align: i32,
338 pub color: Color,
339 pub uri: Option<String>,
340}
341
342#[derive(Debug, Clone)]
343pub struct ImageElement {
344 pub offset: usize,
345 pub width: i32,
346 pub height: i32,
347 pub scale: f32,
348 pub vertical_align: i32,
349 pub display: Display,
350 pub margin: Edge,
351 pub float: Option<Float>,
352 pub path: String,
353 pub uri: Option<String>,
354}
355
356#[derive(Debug, Clone)]
357pub enum DrawCommand {
358 Text(TextCommand),
359 ExtraText(TextCommand),
360 Image(ImageCommand),
361 Marker(usize),
362}
363
364#[derive(Debug, Clone)]
365pub struct TextCommand {
366 pub offset: usize,
367 pub position: Point,
368 pub text: String,
369 pub plan: RenderPlan,
370 pub font_kind: FontKind,
371 pub font_style: FontStyle,
372 pub font_weight: FontWeight,
373 pub font_size: u32,
374 pub color: Color,
375 pub uri: Option<String>,
376 pub rect: Rectangle,
377}
378
379#[derive(Debug, Clone)]
380pub struct ImageCommand {
381 pub offset: usize,
382 pub position: Point,
383 pub scale: f32,
384 pub path: String,
385 pub uri: Option<String>,
386 pub rect: Rectangle,
387}
388
389impl DrawCommand {
390 pub fn offset(&self) -> usize {
391 match *self {
392 DrawCommand::Text(TextCommand { offset, .. }) => offset,
393 DrawCommand::ExtraText(TextCommand { offset, .. }) => offset,
394 DrawCommand::Image(ImageCommand { offset, .. }) => offset,
395 DrawCommand::Marker(offset) => offset,
396 }
397 }
398
399 pub fn rect(&self) -> Option<Rectangle> {
400 match *self {
401 DrawCommand::Text(TextCommand { rect, .. }) => Some(rect),
402 DrawCommand::ExtraText(TextCommand { rect, .. }) => Some(rect),
403 DrawCommand::Image(ImageCommand { rect, .. }) => Some(rect),
404 _ => None,
405 }
406 }
407
408 pub fn position_mut(&mut self) -> Option<&mut Point> {
409 match *self {
410 DrawCommand::Text(TextCommand {
411 ref mut position, ..
412 }) => Some(position),
413 DrawCommand::ExtraText(TextCommand {
414 ref mut position, ..
415 }) => Some(position),
416 DrawCommand::Image(ImageCommand {
417 ref mut position, ..
418 }) => Some(position),
419 _ => None,
420 }
421 }
422}
423
424pub fn collapse_margins(a: i32, b: i32) -> i32 {
425 if a >= 0 && b >= 0 {
426 a.max(b)
427 } else if a < 0 && b < 0 {
428 a.min(b)
429 } else {
430 a + b
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_hyph_lang() {
440 assert_eq!(hyph_lang("zh-latn-pinyin"), Some(Language::Chinese));
441 assert_eq!(hyph_lang("EN"), Some(Language::EnglishUS));
442 assert_eq!(hyph_lang("en-GB"), Some(Language::EnglishGB));
443 assert_eq!(hyph_lang("DE-ZZZ"), Some(Language::German1996));
444 assert_eq!(hyph_lang("de-CH-uuu"), Some(Language::GermanSwiss));
445 assert_eq!(hyph_lang("y"), None);
446 }
447}
448
449pub fn hyph_lang(name: &str) -> Option<Language> {
450 HYPHENATION_LANGUAGES
451 .get(name)
452 .or_else(|| HYPHENATION_LANGUAGES.get(name.to_lowercase().as_str()))
453 .or_else(|| {
454 let name_lc = name.to_lowercase();
455 let mut s = name_lc.as_str();
456 while let Some(index) = s.rfind('-') {
457 s = &s[..index];
458 let opt = HYPHENATION_LANGUAGES.get(s);
459 if opt.is_some() {
460 return opt;
461 }
462 }
463 None
464 })
465 .cloned()
466}
467
468lazy_static! {
469pub static ref HYPHENATION_LANGUAGES: FxHashMap<&'static str, Language> = [
470 ("af", Language::Afrikaans),
471 ("hy", Language::Armenian),
472 ("as", Language::Assamese),
473 ("eu", Language::Basque),
474 ("be", Language::Belarusian),
475 ("bn", Language::Bengali),
476 ("bg", Language::Bulgarian),
477 ("ca", Language::Catalan),
478 ("zh-latn-pinyin", Language::Chinese),
479 ("cop", Language::Coptic),
480 ("hr", Language::Croatian),
481 ("cs", Language::Czech),
482 ("da", Language::Danish),
483 ("nl", Language::Dutch),
484 ("en-gb", Language::EnglishGB),
485 ("en-us", Language::EnglishUS),
486 ("en", Language::EnglishUS),
487 ("eo", Language::Esperanto),
488 ("et", Language::Estonian),
489 ("mul-ethi", Language::Ethiopic),
490 ("fi", Language::Finnish),
491 ("fr", Language::French),
492 ("fur", Language::Friulan),
493 ("gl", Language::Galician),
494 ("ka", Language::Georgian),
495 ("de", Language::German1996),
496 ("de-1901", Language::German1901),
497 ("de-1996", Language::German1996),
498 ("de-ch-1901", Language::GermanSwiss),
499 ("de-ch", Language::GermanSwiss),
500 ("grc", Language::GreekAncient),
501 ("el-monoton", Language::GreekMono),
502 ("el-polyton", Language::GreekPoly),
503 ("gu", Language::Gujarati),
504 ("hi", Language::Hindi),
505 ("hu", Language::Hungarian),
506 ("is", Language::Icelandic),
507 ("id", Language::Indonesian),
508 ("ia", Language::Interlingua),
509 ("ga", Language::Irish),
510 ("it", Language::Italian),
511 ("kn", Language::Kannada),
512 ("kmr", Language::Kurmanji),
513 ("la", Language::Latin),
514 ("la-x-classic", Language::LatinClassic),
515 ("la-x-liturgic", Language::LatinLiturgical),
516 ("lv", Language::Latvian),
517 ("lt", Language::Lithuanian),
518 ("mk", Language::Macedonian),
519 ("ml", Language::Malayalam),
520 ("mr", Language::Marathi),
521 ("mn-cyrl", Language::Mongolian),
522 ("nb", Language::NorwegianBokmal),
523 ("nn", Language::NorwegianNynorsk),
524 ("oc", Language::Occitan),
525 ("or", Language::Oriya),
526 ("pi", Language::Pali),
527 ("pa", Language::Panjabi),
528 ("pms", Language::Piedmontese),
529 ("pl", Language::Polish),
530 ("pt", Language::Portuguese),
531 ("ro", Language::Romanian),
532 ("rm", Language::Romansh),
533 ("ru", Language::Russian),
534 ("sa", Language::Sanskrit),
535 ("sr-cyrl", Language::SerbianCyrillic),
536 ("sh-cyrl", Language::SerbocroatianCyrillic),
537 ("sh-latn", Language::SerbocroatianLatin),
538 ("cu", Language::SlavonicChurch),
539 ("sk", Language::Slovak),
540 ("sl", Language::Slovenian),
541 ("es", Language::Spanish),
542 ("sv", Language::Swedish),
543 ("ta", Language::Tamil),
544 ("te", Language::Telugu),
545 ("th", Language::Thai),
546 ("tr", Language::Turkish),
547 ("tk", Language::Turkmen),
548 ("uk", Language::Ukrainian),
549 ("hsb", Language::Uppersorbian),
550 ("cy", Language::Welsh)].iter().cloned().collect();
551
552pub static ref HYPHENATION_PATTERNS: FxHashMap<Language, Standard> = {
553 let mut map = FxHashMap::default();
554 for lang in HYPHENATION_LANGUAGES.values() {
555 if map.contains_key(lang) {
556 continue;
557 }
558 let base = CURRENT_DEVICE.install_path("hyphenation-patterns")
559 .join(lang.code());
560 let path = base.with_extension("standard.bincode");
561 if let Ok(mut patterns) = Standard::from_path(*lang, path) {
562 let path = base.with_extension("bounds");
563 if let Ok(pair) = fs::read_to_string(path) {
564 let bounds = pair.trim_end().split(' ')
565 .filter_map(|s| s.parse().ok())
566 .collect::<Vec<usize>>();
567 if bounds.len() == 2 {
568 patterns.minima.0 = bounds[0];
569 patterns.minima.1 = bounds[1];
570 }
571 }
572 map.insert(*lang, patterns);
573 }
574 }
575 map
576};
577
578pub static ref EM_SPACE_RATIOS: FxHashMap<char, f32> = [
579 ('\u{2000}', 0.5),
581 ('\u{2001}', 1.0),
583 ('\u{2002}', 0.5),
585 ('\u{2003}', 1.0),
587 ('\u{2004}', 0.33),
589 ('\u{2005}', 0.25),
591 ('\u{2006}', 0.16)].iter().cloned().collect();
593
594pub static ref WORD_SPACE_RATIOS: FxHashMap<char, f32> = [
595 ('\t', 4.0),
597 ('\u{00A0}', 1.0),
599 ('\u{202F}', 0.5),
601 ('\u{2009}', 0.5),
603 ('\u{200A}', 0.25)].iter().cloned().collect();
605}
606
607pub const FONT_SPACES: &str = " \u{2007}\u{2008}";