Skip to content

Commit

Permalink
Prepared height calculation for words and images
Browse files Browse the repository at this point in the history
  • Loading branch information
fschutt committed Oct 12, 2018
1 parent 72c0d2b commit 7b23688
Show file tree
Hide file tree
Showing 6 changed files with 469 additions and 307 deletions.
6 changes: 4 additions & 2 deletions src/app_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,13 @@ impl AppResources {
Some(v) => {
let to_delete_image_key = match *v {
ImageState::Uploaded(ref image_info) => {
Some(image_info.key.clone())
Some((Some(image_info.key.clone()), image_info.descriptor.clone()))
},
_ => None,
};
*v = ImageState::AboutToBeDeleted(to_delete_image_key);
if let Some((key, descriptor)) = to_delete_image_key {
*v = ImageState::AboutToBeDeleted((key, descriptor));
}
Some(())
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/display_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl<'a, T: Layout + 'a> DisplayList<'a, T> {
updated_images.push((key.clone(), d.clone()));
},
ImageState::Uploaded(_) => { },
ImageState::AboutToBeDeleted(ref k) => {
ImageState::AboutToBeDeleted((ref k, _)) => {
to_delete_images.push((key.clone(), k.clone()));
}
}
Expand Down
46 changes: 43 additions & 3 deletions src/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ use std::{
hash::{Hash, Hasher},
sync::atomic::{AtomicUsize, Ordering},
collections::BTreeMap,
iter::FromIterator,
};
use glium::{Texture2d, framebuffer::SimpleFrameBuffer};
use {
FastHashMap,
window::{WindowEvent, WindowInfo},
images::ImageId,
images::{ImageId, ImageState},
cache::DomHash,
text_cache::TextId,
traits::Layout,
app_state::AppState,
id_tree::{NodeId, Node, Arena},
default_callbacks::{DefaultCallbackId, StackCheckedPointer},
window::HidpiAdjustedBounds,
text_layout::{Words, FontMetrics},
};

static TAG_ID: AtomicUsize = AtomicUsize::new(0);
Expand Down Expand Up @@ -265,6 +268,45 @@ impl<T: Layout> NodeType<T> {
IFrame(_) => "iframe",
}
}

/// Returns the preferred width, for example for an image, that would be the
/// original width (an image always wants to take up the original space)
pub(crate) fn get_preferred_width(&self, image_cache: &FastHashMap<ImageId, ImageState>) -> Option<f32> {
use self::NodeType::*;
match self {
Image(i) => image_cache.get(i).and_then(|image_state| Some(image_state.get_dimensions().0)),
Label(_) | Text(_) => /* TODO: Calculate the minimum width for the text? */ None,
_ => None,
}
}

/// Given a certain width, returns the
pub(crate) fn get_preferred_height_based_on_width(
&self,
div_width: f32,
image_cache: &FastHashMap<ImageId, ImageState>,
words: Option<(&Words, &FontMetrics)>,
) -> Option<f32>
{
use self::NodeType::*;
use css_parser::{LayoutOverflow, TextOverflowBehaviour, TextOverflowBehaviourInner};

match self {
Image(i) => image_cache.get(i).and_then(|image_state| {
let (image_original_height, image_original_width) = image_state.get_dimensions();
Some((image_original_width / image_original_height) * div_width)
}),
Label(_) | Text(_) => {
let (words, font) = words.unwrap();
let vertical_info = words.get_vertical_height(&LayoutOverflow {
horizontal: TextOverflowBehaviour::Modified(TextOverflowBehaviourInner::Scroll),
.. Default::default()
}, font, div_width);
Some(vertical_info.vertical_height)
}
_ => None,
}
}
}

/// OpenGL texture, use `ReadOnlyWindow::create_texture` to create a texture
Expand Down Expand Up @@ -575,8 +617,6 @@ impl<T: Layout> CallbackList<T> {
}
}

use std::iter::FromIterator;

impl<T: Layout> FromIterator<Dom<T>> for Dom<T> {
fn from_iter<I: IntoIterator<Item=Dom<T>>>(iter: I) -> Self {
let mut c = Dom::new(NodeType::Div);
Expand Down
13 changes: 12 additions & 1 deletion src/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,20 @@ pub(crate) enum ImageState {
// image is loaded & decoded, but not yet available
ReadyForUpload((ImageData, ImageDescriptor)),
// Image is about to get deleted in the next frame
AboutToBeDeleted(Option<ImageKey>),
AboutToBeDeleted((Option<ImageKey>, ImageDescriptor)),
}

impl ImageState {
/// Returns the original dimensions of the image
pub fn get_dimensions(&self) -> (f32, f32) {
use self::ImageState::*;
match self {
Uploaded(ImageInfo { descriptor, .. }) |
ReadyForUpload((_, descriptor)) |
AboutToBeDeleted((_, descriptor)) => (descriptor.size.width as f32, descriptor.size.height as f32)
}
}
}

impl ImageType {

Expand Down
112 changes: 64 additions & 48 deletions src/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,58 @@ pub struct Words {
pub longest_word_width: f32,
}

impl Words {
/// Given a width, returns the vertical height of the text (no vertical overflow checks)
pub fn get_vertical_height(&self, overflow: &LayoutOverflow, font_metrics: &FontMetrics, width: f32)
-> VerticalTextInfo
{
use self::SemanticWordItem::*;

let FontMetrics { space_width, tab_width, vertical_advance, .. } = *font_metrics;

if overflow.allows_horizontal_overflow() {
// If we can overflow horizontally, we only need to sum up the `Return`
// characters, since the actual length of the line doesn't matter
VerticalTextInfo {
vertical_height: self.items.iter().filter(|w| w.is_return()).count() as f32 * vertical_advance,
max_hor_len: None
}
} else {
// TODO: should this be cached? The calculation is probably quick, but this
// is essentially the same thing as we do in the actual text layout stage
let mut max_line_cursor: f32 = 0.0;
let mut cur_line_cursor = 0.0;
// Start at line 1 because we always have one line and not zero.
let mut cur_line = 1;

for w in &self.items {
match w {
Word(w) => {
if cur_line_cursor + w.total_width > width {
max_line_cursor = max_line_cursor.max(cur_line_cursor);
cur_line_cursor = 0.0;
cur_line += 1;
}
cur_line_cursor += w.total_width + space_width;
},
// TODO: also check for rect break after tabs? Kinda pointless, isn't it?
Tab => cur_line_cursor += tab_width,
Return => {
max_line_cursor = max_line_cursor.max(cur_line_cursor);
cur_line_cursor = 0.0;
cur_line += 1;
}
}
}

VerticalTextInfo {
max_hor_len: Some(cur_line_cursor),
vertical_height: cur_line as f32 * vertical_advance * PT_TO_PX,
}
}
}
}

/// A `Word` contains information about the layout of a single word
#[derive(Debug, Clone)]
pub struct Word {
Expand Down Expand Up @@ -524,6 +576,12 @@ pub(crate) fn split_text_into_words<'a>(text: &str, font: &Font<'a>, font_size:
}
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct VerticalTextInfo {
pub vertical_height: f32,
pub max_hor_len: Option<f32>,
}

// First pass: calculate if the words will overflow (using the tabs)
fn estimate_overflow_pass_1(
words: &Words,
Expand All @@ -534,10 +592,10 @@ fn estimate_overflow_pass_1(
{
use self::SemanticWordItem::*;

let words = &words.items;
let VerticalTextInfo { vertical_height, max_hor_len } = words.get_vertical_height(overflow, font_metrics, rect_dimensions.width);

let words = &words.items;
let FontMetrics { space_width, tab_width, vertical_advance, .. } = *font_metrics;

let max_text_line_len_horizontal = 0.0;

// Determine the maximum width and height that the text needs for layout
Expand All @@ -552,52 +610,10 @@ fn estimate_overflow_pass_1(
// Horizontal scrollbars, on the other hand, are 1.0x the width of the rectangle,
// when the width is filled.

// TODO: this is duplicated code

let mut max_hor_len = None;

let vertical_length = {
if overflow.allows_horizontal_overflow() {
// If we can overflow horizontally, we only need to sum up the `Return`
// characters, since the actual length of the line doesn't matter
words.iter().filter(|w| w.is_return()).count() as f32 * vertical_advance
} else {
// TODO: should this be cached? The calculation is probably quick, but this
// is essentially the same thing as we do in the actual text layout stage
let mut max_line_cursor: f32 = 0.0;
let mut cur_line_cursor = 0.0;
// Start at line 1 because we always have one line and not zero.
let mut cur_line = 1;

for w in words {
match w {
Word(w) => {
if cur_line_cursor + w.total_width > rect_dimensions.width {
max_line_cursor = max_line_cursor.max(cur_line_cursor);
cur_line_cursor = 0.0;
cur_line += 1;
}
cur_line_cursor += w.total_width + space_width;
},
// TODO: also check for rect break after tabs? Kinda pointless, isn't it?
Tab => cur_line_cursor += tab_width,
Return => {
max_line_cursor = max_line_cursor.max(cur_line_cursor);
cur_line_cursor = 0.0;
cur_line += 1;
}
}
}

max_hor_len = Some(cur_line_cursor);
cur_line as f32 * vertical_advance * PT_TO_PX
}
};

let vertical_length = if vertical_length > rect_dimensions.height {
TextOverflow::IsOverflowing(vertical_length - rect_dimensions.height)
let vertical_height = if vertical_height > rect_dimensions.height {
TextOverflow::IsOverflowing(vertical_height - rect_dimensions.height)
} else {
TextOverflow::InBounds(rect_dimensions.height - vertical_length)
TextOverflow::InBounds(rect_dimensions.height - vertical_height)
};

let horizontal_length = {
Expand Down Expand Up @@ -632,7 +648,7 @@ fn estimate_overflow_pass_1(

TextOverflowPass1 {
horizontal: horizontal_length,
vertical: vertical_length,
vertical: vertical_height,
}
}

Expand Down
Loading

0 comments on commit 7b23688

Please sign in to comment.