diff options
author | RazrFalcon <razrfalcon@gmail.com> | 2018-05-29 13:52:14 +0300 |
---|---|---|
committer | RazrFalcon <razrfalcon@gmail.com> | 2018-05-29 13:52:14 +0300 |
commit | 744f60f8278455e8d7ec78bc602ec7c2c0d43c59 (patch) | |
tree | 7bfeeaa4142f80004cba9e1bb8c7c1b56e7340c1 /src | |
parent | c0fcac4258c0c6c4ffb96d828c2871a3805d116b (diff) |
Refactoring.
(cairo-backend) Fixed text layout.
Diffstat (limited to 'src')
-rw-r--r-- | src/backend_cairo/mod.rs | 3 | ||||
-rw-r--r-- | src/backend_cairo/text.rs | 200 | ||||
-rw-r--r-- | src/backend_qt/mod.rs | 8 | ||||
-rw-r--r-- | src/backend_qt/text.rs | 186 | ||||
-rw-r--r-- | src/backend_utils/mod.rs | 1 | ||||
-rw-r--r-- | src/backend_utils/text.rs | 161 |
6 files changed, 254 insertions, 305 deletions
diff --git a/src/backend_cairo/mod.rs b/src/backend_cairo/mod.rs index b945284..187dc59 100644 --- a/src/backend_cairo/mod.rs +++ b/src/backend_cairo/mod.rs @@ -375,8 +375,9 @@ fn _calc_node_bbox( } usvg::NodeKind::Text(ref text) => { let mut bbox = Rect::new_bbox(); + let mut fm = text::PangoFontMetrics::new(opt, cr); - text::draw_blocks(text, node, opt, cr, |block| { + text::draw_blocks(text, node, &mut fm, |block| { cr.new_path(); let context = text::init_pango_context(opt, cr); diff --git a/src/backend_cairo/text.rs b/src/backend_cairo/text.rs index 2bbf244..5810602 100644 --- a/src/backend_cairo/text.rs +++ b/src/backend_cairo/text.rs @@ -5,7 +5,6 @@ use std::f64; // external -use unicode_segmentation::UnicodeSegmentation; use cairo; use pango::{ self, @@ -18,11 +17,16 @@ use usvg::prelude::*; // self use super::prelude::*; +use backend_utils::text::{ + self, + FontMetrics, +}; use super::{ fill, stroke, }; +pub use backend_utils::text::draw_blocks; trait PangoScale { fn scale(&self) -> f64; @@ -34,15 +38,42 @@ impl PangoScale for i32 { } } +pub struct PangoFontMetrics { + layout: pango::Layout, + dpi: f64, +} + +impl PangoFontMetrics { + pub fn new(opt: &Options, cr: &cairo::Context) -> Self { + let context = init_pango_context(opt, cr); + let layout = pango::Layout::new(&context); + PangoFontMetrics { layout, dpi: opt.usvg.dpi } + } +} + +impl FontMetrics<pango::FontDescription> for PangoFontMetrics { + fn set_font(&mut self, font: &usvg::Font) { + let font = init_font(font, self.dpi); + self.layout.set_font_description(&font); + } + + fn font(&self) -> pango::FontDescription { + self.layout.get_font_description().unwrap() + } + + fn width(&self, text: &str) -> f64 { + self.layout.set_text(text); + self.layout.get_size().0.scale() + } + + fn ascent(&self) -> f64 { + let mut layout_iter = self.layout.get_iter().unwrap(); + layout_iter.get_baseline().scale() + } -pub struct TextBlock { - pub text: String, - pub bbox: Rect, - pub rotate: f64, - pub fill: Option<usvg::Fill>, - pub stroke: Option<usvg::Stroke>, - pub font: pango::FontDescription, - pub decoration: usvg::TextDecoration, + fn height(&self) -> f64 { + self.layout.get_size().1.scale() + } } pub fn draw( @@ -51,158 +82,15 @@ pub fn draw( cr: &cairo::Context, ) -> Rect { let tree = &node.tree(); + let mut fm = PangoFontMetrics::new(opt, cr); + if let usvg::NodeKind::Text(ref text) = *node.borrow() { - draw_blocks(text, node, opt, cr, |block| draw_block(tree, block, opt, cr)) + draw_blocks(text, node, &mut fm, |block| draw_block(tree, block, opt, cr)) } else { unreachable!(); } } -// TODO: find a way to merge this with a Qt backend -pub fn draw_blocks<DrawAt>( - text_kind: &usvg::Text, - node: &usvg::Node, - opt: &Options, - cr: &cairo::Context, - mut draw: DrawAt, -) -> Rect - where DrawAt: FnMut(&TextBlock) -{ - fn first_number_or(list: &Option<usvg::NumberList>, def: f64) -> f64 { - list.as_ref().map(|list| list[0]).unwrap_or(def) - } - - let mut blocks: Vec<TextBlock> = Vec::new(); - let mut last_x = 0.0; - let mut last_y = 0.0; - for chunk_node in node.children() { - let kind = chunk_node.borrow(); - let chunk = match *kind { - usvg::NodeKind::TextChunk(ref chunk) => chunk, - _ => continue, - }; - - let mut chunk_x = first_number_or(&chunk.x, last_x); - let mut x = chunk_x; - let mut y = first_number_or(&chunk.y, last_y); - let start_idx = blocks.len(); - let mut grapheme_idx = 0; - - for tspan_node in chunk_node.children() { - let kind = tspan_node.borrow(); - let tspan = match *kind { - usvg::NodeKind::TSpan(ref tspan) => tspan, - _ => continue, - }; - - let context = init_pango_context(opt, cr); - let font = init_font(&tspan.font, opt.usvg.dpi); - let layout = pango::Layout::new(&context); - layout.set_font_description(&font); - - let iter = UnicodeSegmentation::graphemes(tspan.text.as_str(), true); - for (i, c) in iter.enumerate() { - let mut has_custom_offset = i == 0; - - { - let mut number_at = |list: &Option<usvg::NumberList>| -> Option<f64> { - if let &Some(ref list) = list { - if let Some(n) = list.get(grapheme_idx) { - has_custom_offset = true; - return Some(*n); - } - } - - None - }; - - if let Some(n) = number_at(&chunk.x) { x = n; } - if let Some(n) = number_at(&chunk.y) { y = n; } - if let Some(n) = number_at(&chunk.dx) { x += n; } - if let Some(n) = number_at(&chunk.dy) { y += n; } - - if i == 0 { - if let Some(n) = number_at(&chunk.x) { chunk_x = n; } - if let Some(n) = number_at(&chunk.dx) { chunk_x += n; } - } - } - - if text_kind.rotate.is_some() { - has_custom_offset = true; - } - - let can_merge = !blocks.is_empty() && !has_custom_offset; - if can_merge { - let prev_idx = blocks.len() - 1; - blocks[prev_idx].text.push_str(c); - - layout.set_text(&blocks[prev_idx].text); - let w = layout.get_size().0.scale(); - blocks[prev_idx].bbox.width = w; - - let mut new_w = chunk_x; - for i in start_idx..blocks.len() { - new_w += blocks[i].bbox.width; - } - - x = new_w; - } else { - let mut layout_iter = layout.get_iter().unwrap(); - let yy = y - layout_iter.get_baseline().scale(); - let height = layout.get_size().1.scale(); - - layout.set_text(c); - let width = layout.get_size().0.scale(); - - let bbox = Rect { x, y: yy, width, height }; - x += width; - - let rotate = match text_kind.rotate { - Some(ref list) => { list[blocks.len()] } - None => 0.0, - }; - - blocks.push(TextBlock { - text: c.to_string(), - bbox, - rotate, - fill: tspan.fill.clone(), - stroke: tspan.stroke.clone(), - font: font.clone(), - decoration: tspan.decoration.clone(), - }); - } - - grapheme_idx += 1; - } - } - - let mut chunk_w = 0.0; - for i in start_idx..blocks.len() { - chunk_w += blocks[i].bbox.width; - } - - let adx = utils::process_text_anchor(chunk.anchor, chunk_w); - for i in start_idx..blocks.len() { - blocks[i].bbox.x -= adx; - } - - last_x = chunk_x + chunk_w - adx; - last_y = y; - } - - let mut bbox = Rect::new_bbox(); - for block in blocks { - bbox.expand(block.bbox); - draw(&block); - } - - if bbox.x == f64::MAX { bbox.x = 0.0; } - if bbox.y == f64::MAX { bbox.y = 0.0; } - - bbox -} - pub fn init_pango_context(opt: &Options, cr: &cairo::Context) -> pango::Context { let context = pc::create_context(cr).unwrap(); pc::update_context(cr, &context); @@ -223,7 +111,7 @@ pub fn init_pango_layout( fn draw_block( tree: &usvg::Tree, - block: &TextBlock, + block: &text::TextBlock<pango::FontDescription>, opt: &Options, cr: &cairo::Context, ) { diff --git a/src/backend_qt/mod.rs b/src/backend_qt/mod.rs index b29a42f..8955aec 100644 --- a/src/backend_qt/mod.rs +++ b/src/backend_qt/mod.rs @@ -323,12 +323,13 @@ pub fn calc_node_bbox( let p = qt::Painter::new(&img); let abs_ts = utils::abs_transform(node); - _calc_node_bbox(node, abs_ts, &p) + _calc_node_bbox(node, abs_ts, opt, &p) } fn _calc_node_bbox( node: &usvg::Node, ts: usvg::Transform, + opt: &Options, p: &qt::Painter, ) -> Option<Rect> { let mut ts2 = ts; @@ -340,8 +341,9 @@ fn _calc_node_bbox( } usvg::NodeKind::Text(ref text) => { let mut bbox = Rect::new_bbox(); + let mut fm = text::QtFontMetrics::new(p); - text::draw_blocks(text, node, p, |block| { + text::draw_blocks(text, node, &mut fm, |block| { let mut p_path = qt::PainterPath::new(); p.set_font(&block.font); @@ -365,7 +367,7 @@ fn _calc_node_bbox( let mut bbox = Rect::new_bbox(); for child in node.children() { - if let Some(c_bbox) = _calc_node_bbox(&child, ts2, p) { + if let Some(c_bbox) = _calc_node_bbox(&child, ts2, opt, p) { bbox.expand(c_bbox); } } diff --git a/src/backend_qt/text.rs b/src/backend_qt/text.rs index bd919c3..3919764 100644 --- a/src/backend_qt/text.rs +++ b/src/backend_qt/text.rs @@ -3,26 +3,55 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. // external -use unicode_segmentation::UnicodeSegmentation; use qt; use usvg; use usvg::prelude::*; // self use super::prelude::*; +use backend_utils::text::{ + self, + FontMetrics, +}; use super::{ fill, stroke, }; -pub struct TextBlock { - pub text: String, - pub bbox: Rect, - pub rotate: f64, - pub fill: Option<usvg::Fill>, - pub stroke: Option<usvg::Stroke>, - pub font: qt::Font, - pub decoration: usvg::TextDecoration, +pub use backend_utils::text::draw_blocks; + + +pub struct QtFontMetrics<'a> { + p: &'a qt::Painter, +} + +impl<'a> QtFontMetrics<'a> { + pub fn new(p: &'a qt::Painter) -> Self { + QtFontMetrics { p } + } +} + +impl<'a> FontMetrics<qt::Font> for QtFontMetrics<'a> { + fn set_font(&mut self, font: &usvg::Font) { + let font = init_font(font); + self.p.set_font(&font); + } + + fn font(&self) -> qt::Font { + self.p.font() + } + + fn width(&self, text: &str) -> f64 { + self.p.font_metrics().width(text) + } + + fn ascent(&self) -> f64 { + self.p.font_metrics().ascent() + } + + fn height(&self) -> f64 { + self.p.font_metrics().height() + } } pub fn draw( @@ -31,151 +60,18 @@ pub fn draw( p: &qt::Painter, ) -> Rect { let tree = &node.tree(); + let mut fm = QtFontMetrics::new(p); if let usvg::NodeKind::Text(ref text) = *node.borrow() { - draw_blocks(text, node, p, |block| draw_block(tree, block, opt, p)) + draw_blocks(text, node, &mut fm, |block| draw_block(tree, block, opt, p)) } else { unreachable!(); } } -// TODO: find a way to merge this with a cairo backend -pub fn draw_blocks<DrawAt>( - text_kind: &usvg::Text, - node: &usvg::Node, - p: &qt::Painter, - mut draw: DrawAt -) -> Rect - where DrawAt: FnMut(&TextBlock) -{ - fn first_number_or(list: &Option<usvg::NumberList>, def: f64) -> f64 { - list.as_ref().map(|list| list[0]).unwrap_or(def) - } - - let mut blocks: Vec<TextBlock> = Vec::new(); - let mut last_x = 0.0; - let mut last_y = 0.0; - for chunk_node in node.children() { - let kind = chunk_node.borrow(); - let chunk = match *kind { - usvg::NodeKind::TextChunk(ref chunk) => chunk, - _ => continue, - }; - - let mut chunk_x = first_number_or(&chunk.x, last_x); - let mut x = chunk_x; - let mut y = first_number_or(&chunk.y, last_y); - let start_idx = blocks.len(); - let mut grapheme_idx = 0; - - for tspan_node in chunk_node.children() { - let kind = tspan_node.borrow(); - let tspan = match *kind { - usvg::NodeKind::TSpan(ref tspan) => tspan, - _ => continue, - }; - - let font = init_font(&tspan.font); - p.set_font(&font); - let font_metrics = p.font_metrics(); - - let iter = UnicodeSegmentation::graphemes(tspan.text.as_str(), true); - for (i, c) in iter.enumerate() { - let mut has_custom_offset = i == 0; - - { - let mut number_at = |list: &Option<usvg::NumberList>| -> Option<f64> { - if let &Some(ref list) = list { - if let Some(n) = list.get(grapheme_idx) { - has_custom_offset = true; - return Some(*n); - } - } - - None - }; - - if let Some(n) = number_at(&chunk.x) { x = n; } - if let Some(n) = number_at(&chunk.y) { y = n; } - if let Some(n) = number_at(&chunk.dx) { x += n; } - if let Some(n) = number_at(&chunk.dy) { y += n; } - - if i == 0 { - if let Some(n) = number_at(&chunk.x) { chunk_x = n; } - if let Some(n) = number_at(&chunk.dx) { chunk_x += n; } - } - } - - if text_kind.rotate.is_some() { - has_custom_offset = true; - } - - let can_merge = !blocks.is_empty() && !has_custom_offset; - if can_merge { - let prev_idx = blocks.len() - 1; - blocks[prev_idx].text.push_str(c); - let w = font_metrics.width(&blocks[prev_idx].text); - blocks[prev_idx].bbox.width = w; - - let mut new_w = chunk_x; - for i in start_idx..blocks.len() { - new_w += blocks[i].bbox.width; - } - - x = new_w; - } else { - let yy = y - font_metrics.ascent(); - let height = font_metrics.height(); - let width = font_metrics.width(c); - let bbox = Rect { x, y: yy, width, height }; - x += width; - - let rotate = match text_kind.rotate { - Some(ref list) => { list[blocks.len()] } - None => 0.0, - }; - - blocks.push(TextBlock { - text: c.to_string(), - bbox, - rotate, - fill: tspan.fill.clone(), - stroke: tspan.stroke.clone(), - font: init_font(&tspan.font), // TODO: clone - decoration: tspan.decoration.clone(), - }); - } - - grapheme_idx += 1; - } - } - - let mut chunk_w = 0.0; - for i in start_idx..blocks.len() { - chunk_w += blocks[i].bbox.width; - } - - let adx = utils::process_text_anchor(chunk.anchor, chunk_w); - for i in start_idx..blocks.len() { - blocks[i].bbox.x -= adx; - } - - last_x = chunk_x + chunk_w - adx; - last_y = y; - } - - let mut bbox = Rect::new_bbox(); - for block in blocks { - bbox.expand(block.bbox); - draw(&block); - } - - bbox -} - fn draw_block( tree: &usvg::Tree, - block: &TextBlock, + block: &text::TextBlock<qt::Font>, opt: &Options, p: &qt::Painter, ) { diff --git a/src/backend_utils/mod.rs b/src/backend_utils/mod.rs index 97f54aa..37a18d0 100644 --- a/src/backend_utils/mod.rs +++ b/src/backend_utils/mod.rs @@ -4,3 +4,4 @@ pub mod image; pub mod mask; +pub mod text; diff --git a/src/backend_utils/text.rs b/src/backend_utils/text.rs new file mode 100644 index 0000000..82ac48e --- /dev/null +++ b/src/backend_utils/text.rs @@ -0,0 +1,161 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// external +use unicode_segmentation::UnicodeSegmentation; +use usvg; + +// self +use geom::*; +use utils; + + +pub struct TextBlock<Font> { + pub text: String, + pub bbox: Rect, + pub rotate: f64, + pub fill: Option<usvg::Fill>, + pub stroke: Option<usvg::Stroke>, + pub font: Font, + pub decoration: usvg::TextDecoration, +} + +pub trait FontMetrics<Font> { + fn set_font(&mut self, font: &usvg::Font); + fn font(&self) -> Font; + fn width(&self, text: &str) -> f64; + fn ascent(&self) -> f64; + fn height(&self) -> f64; +} + +pub fn draw_blocks<Font, DrawAt>( + text_kind: &usvg::Text, + node: &usvg::Node, + font_metrics: &mut FontMetrics<Font>, + mut draw: DrawAt +) -> Rect + where DrawAt: FnMut(&TextBlock<Font>) +{ + fn first_number_or(list: &Option<usvg::NumberList>, def: f64) -> f64 { + list.as_ref().map(|list| list[0]).unwrap_or(def) + } + + let mut blocks: Vec<TextBlock<Font>> = Vec::new(); + let mut last_x = 0.0; + let mut last_y = 0.0; + for chunk_node in node.children() { + let kind = chunk_node.borrow(); + let chunk = match *kind { + usvg::NodeKind::TextChunk(ref chunk) => chunk, + _ => continue, + }; + + let mut chunk_x = first_number_or(&chunk.x, last_x); + let mut x = chunk_x; + let mut y = first_number_or(&chunk.y, last_y); + let start_idx = blocks.len(); + let mut grapheme_idx = 0; + + for tspan_node in chunk_node.children() { + let kind = tspan_node.borrow(); + let tspan = match *kind { + usvg::NodeKind::TSpan(ref tspan) => tspan, + _ => continue, + }; + + font_metrics.set_font(&tspan.font); + + let iter = UnicodeSegmentation::graphemes(tspan.text.as_str(), true); + for (i, c) in iter.enumerate() { + let mut has_custom_offset = i == 0; + + { + let mut number_at = |list: &Option<usvg::NumberList>| -> Option<f64> { + if let &Some(ref list) = list { + if let Some(n) = list.get(grapheme_idx) { + has_custom_offset = true; + return Some(*n); + } + } + + None + }; + + if let Some(n) = number_at(&chunk.x) { x = n; } + if let Some(n) = number_at(&chunk.y) { y = n; } + if let Some(n) = number_at(&chunk.dx) { x += n; } + if let Some(n) = number_at(&chunk.dy) { y += n; } + + if i == 0 { + if let Some(n) = number_at(&chunk.x) { chunk_x = n; } + if let Some(n) = number_at(&chunk.dx) { chunk_x += n; } + } + } + + if text_kind.rotate.is_some() { + has_custom_offset = true; + } + + let can_merge = !blocks.is_empty() && !has_custom_offset; + if can_merge { + let prev_idx = blocks.len() - 1; + blocks[prev_idx].text.push_str(c); + let w = font_metrics.width(&blocks[prev_idx].text); + blocks[prev_idx].bbox.width = w; + + let mut new_w = chunk_x; + for i in start_idx..blocks.len() { + new_w += blocks[i].bbox.width; + } + + x = new_w; + } else { + let width = font_metrics.width(c); + let yy = y - font_metrics.ascent(); + let height = font_metrics.height(); + let bbox = Rect { x, y: yy, width, height }; + x += width; + + let rotate = match text_kind.rotate { + Some(ref list) => { list[blocks.len()] } + None => 0.0, + }; + + blocks.push(TextBlock { + text: c.to_string(), + bbox, + rotate, + fill: tspan.fill.clone(), + stroke: tspan.stroke.clone(), + font: font_metrics.font(), + decoration: tspan.decoration.clone(), + }); + } + + grapheme_idx += 1; + } + } + + let mut chunk_w = 0.0; + for i in start_idx..blocks.len() { + chunk_w += blocks[i].bbox.width; + } + + let adx = utils::process_text_anchor(chunk.anchor, chunk_w); + for i in start_idx..blocks.len() { + blocks[i].bbox.x -= adx; + } + + last_x = chunk_x + chunk_w - adx; + last_y = y; + } + + let mut bbox = Rect::new_bbox(); + for block in blocks { + bbox.expand(block.bbox); + draw(&block); + } + + bbox +} |