Add an internal representation of text spans.

Despite having HTML and CSS rendering capabilities, the Flash text field actually does not use HTML as it's internal representation. Instead, the text-span format implied by `getTextFormat` and `setTextFormat` is used to drive layout. You can see this by watching what happens to `htmlText`, *especially* when you add and remove stylesheets.

The `LayoutBox` machinery will be adapted to consume text spans in a future commit. This would make the entire rendering pipeline: HTML/CSS -> Text Spans -> Layout Boxes -> Render commands.
This commit is contained in:
David Wendt 2020-05-06 23:30:57 -04:00
parent 850831181c
commit 1671fc6eba
1 changed files with 182 additions and 0 deletions

View File

@ -264,3 +264,185 @@ impl TextFormat {
Ok(object.into())
}
}
/// Represents the application of a `TextFormat` to a particular text span.
///
/// The actual string data is not stored here; a `TextSpan` is meaningless
/// without it's underlying string content. Furthermore, the start position
/// within the string is implicit in the sum of all previous text span's
/// lengths. See `TextSpans` for more information.
///
/// This struct also contains a resolved version of the `TextFormat` structure
/// listed above.
#[derive(Clone, Debug, Collect)]
#[collect(require_static)]
pub struct TextSpan {
/// How many characters are subsumed by this text span.
///
/// This value must not cause the resulting set of text spans to exceed the
/// length of the underlying source string.
span_length: usize,
font: String,
size: f64,
color: swf::Color,
align: swf::TextAlign,
bold: bool,
italic: bool,
underline: bool,
left_margin: f64,
right_margin: f64,
indent: f64,
block_indent: f64,
kerning: bool,
leading: f64,
letter_spacing: f64,
tab_stops: Vec<f64>,
bullet: bool,
url: String,
target: String,
}
impl TextSpan {
/// Split the text span in two at a particular point relative to the
/// current text span's start.
///
/// The second span is returned and should be inserted into the list of
/// text spans appropriately. The first text span is changed in-line.
///
/// If the split point exceeds the size of the current span, then no span
/// will be returned and no change will be made to the existing span.
fn split_at(&mut self, split_point: usize) -> Option<Self> {
if self.span_length <= split_point || split_point == 0 {
return None;
}
let mut new_span = self.clone();
new_span.span_length = self.span_length - split_point;
self.span_length = split_point;
Some(new_span)
}
/// Determine if this and another span have identical text formats.
///
/// It is assumed that the two text spans being considered are adjacent;
/// and we have no way of checking, so this function doesn't check that.
#[allow(clippy::float_cmp)]
fn can_merge(&self, rhs: &Self) -> bool {
self.font == rhs.font
&& self.size == rhs.size
&& self.color == rhs.color
&& self.align == rhs.align
&& self.bold == rhs.bold
&& self.italic == rhs.italic
&& self.underline == rhs.underline
&& self.left_margin == rhs.left_margin
&& self.right_margin == rhs.right_margin
&& self.indent == rhs.indent
&& self.block_indent == rhs.block_indent
&& self.kerning == rhs.kerning
&& self.leading == rhs.leading
&& self.letter_spacing == rhs.letter_spacing
&& self.tab_stops == rhs.tab_stops
&& self.bullet == rhs.bullet
&& self.url == rhs.url
&& self.target == rhs.target
}
/// Merge two spans together.
///
/// This function assumes the two spans are adjacent; if they are not, this
/// will break invariants of the text span system.
///
/// If the spans do not have identical text formatting, this function will
/// refuse to merge them (see `can_merge`) and return the original `rhs`
/// span. If it consumes the span, and yields None, then the merge
/// completed successfully.
fn merge(&mut self, rhs: Self) -> Option<Self> {
if !self.can_merge(&rhs) {
return Some(rhs);
}
self.span_length += rhs.span_length;
None
}
/// Apply a text format to this text span.
///
/// Properties marked `None` on the `TextFormat` will remain unchanged.
fn set_text_format(&mut self, tf: &TextFormat) {
if let Some(font) = &tf.font {
self.font = font.clone();
}
if let Some(size) = &tf.size {
self.size = *size;
}
if let Some(color) = &tf.color {
self.color = color.clone();
}
if let Some(align) = &tf.align {
self.align = *align;
}
if let Some(bold) = &tf.bold {
self.bold = *bold;
}
if let Some(italic) = &tf.italic {
self.italic = *italic;
}
if let Some(underline) = &tf.underline {
self.underline = *underline;
}
if let Some(left_margin) = &tf.left_margin {
self.left_margin = *left_margin;
}
if let Some(right_margin) = &tf.right_margin {
self.right_margin = *right_margin;
}
if let Some(indent) = &tf.indent {
self.indent = *indent;
}
if let Some(block_indent) = &tf.block_indent {
self.block_indent = *block_indent;
}
if let Some(kerning) = &tf.kerning {
self.kerning = *kerning;
}
if let Some(leading) = &tf.leading {
self.leading = *leading;
}
if let Some(letter_spacing) = &tf.letter_spacing {
self.letter_spacing = *letter_spacing;
}
if let Some(tab_stops) = &tf.tab_stops {
self.tab_stops = tab_stops.clone();
}
if let Some(bullet) = &tf.bullet {
self.bullet = *bullet;
}
if let Some(url) = &tf.url {
self.url = url.clone();
}
if let Some(target) = &tf.target {
self.target = target.clone();
}
}
}