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