aboutsummaryrefslogtreecommitdiffstats
path: root/components/gfx/text/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/gfx/text/util.rs')
-rw-r--r--components/gfx/text/util.rs285
1 files changed, 285 insertions, 0 deletions
diff --git a/components/gfx/text/util.rs b/components/gfx/text/util.rs
new file mode 100644
index 00000000000..c5059bbff10
--- /dev/null
+++ b/components/gfx/text/util.rs
@@ -0,0 +1,285 @@
+/* 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/. */
+
+use text::glyph::CharIndex;
+
+#[deriving(PartialEq)]
+pub enum CompressionMode {
+ CompressNone,
+ CompressWhitespace,
+ CompressWhitespaceNewline,
+ DiscardNewline
+}
+
+// ported from Gecko's nsTextFrameUtils::TransformText.
+//
+// High level TODOs:
+//
+// * Issue #113: consider incoming text state (arabic, etc)
+// and propogate outgoing text state (dual of above)
+//
+// * Issue #114: record skipped and kept chars for mapping original to new text
+//
+// * Untracked: various edge cases for bidi, CJK, etc.
+pub fn transform_text(text: &str, mode: CompressionMode,
+ incoming_whitespace: bool,
+ new_line_pos: &mut Vec<CharIndex>) -> (String, bool) {
+ let mut out_str = String::new();
+ let out_whitespace = match mode {
+ CompressNone | DiscardNewline => {
+ let mut new_line_index = CharIndex(0);
+ for ch in text.chars() {
+ if is_discardable_char(ch, mode) {
+ // TODO: record skipped char
+ } else {
+ // TODO: record kept char
+ if ch == '\t' {
+ // TODO: set "has tab" flag
+ } else if ch == '\n' {
+ // Save new-line's position for line-break
+ // This value is relative(not absolute)
+ new_line_pos.push(new_line_index);
+ new_line_index = CharIndex(0);
+ }
+
+ if ch != '\n' {
+ new_line_index = new_line_index + CharIndex(1);
+ }
+ out_str.push_char(ch);
+ }
+ }
+ text.len() > 0 && is_in_whitespace(text.char_at_reverse(0), mode)
+ },
+
+ CompressWhitespace | CompressWhitespaceNewline => {
+ let mut in_whitespace: bool = incoming_whitespace;
+ for ch in text.chars() {
+ // TODO: discard newlines between CJK chars
+ let mut next_in_whitespace: bool = is_in_whitespace(ch, mode);
+
+ if !next_in_whitespace {
+ if is_always_discardable_char(ch) {
+ // revert whitespace setting, since this char was discarded
+ next_in_whitespace = in_whitespace;
+ // TODO: record skipped char
+ } else {
+ // TODO: record kept char
+ out_str.push_char(ch);
+ }
+ } else { /* next_in_whitespace; possibly add a space char */
+ if in_whitespace {
+ // TODO: record skipped char
+ } else {
+ // TODO: record kept char
+ out_str.push_char(' ');
+ }
+ }
+ // save whitespace context for next char
+ in_whitespace = next_in_whitespace;
+ } /* /for str::each_char */
+ in_whitespace
+ }
+ };
+
+ return (out_str.into_string(), out_whitespace);
+
+ fn is_in_whitespace(ch: char, mode: CompressionMode) -> bool {
+ match (ch, mode) {
+ (' ', _) => true,
+ ('\t', _) => true,
+ ('\n', CompressWhitespaceNewline) => true,
+ (_, _) => false
+ }
+ }
+
+ fn is_discardable_char(ch: char, mode: CompressionMode) -> bool {
+ if is_always_discardable_char(ch) {
+ return true;
+ }
+ match mode {
+ DiscardNewline | CompressWhitespaceNewline => ch == '\n',
+ _ => false
+ }
+ }
+
+ fn is_always_discardable_char(_ch: char) -> bool {
+ // TODO: check for bidi control chars, soft hyphens.
+ false
+ }
+}
+
+pub fn float_to_fixed(before: int, f: f64) -> i32 {
+ (1i32 << before as uint) * (f as i32)
+}
+
+pub fn fixed_to_float(before: int, f: i32) -> f64 {
+ f as f64 * 1.0f64 / ((1i32 << before as uint) as f64)
+}
+
+pub fn fixed_to_rounded_int(before: int, f: i32) -> int {
+ let half = 1i32 << (before-1) as uint;
+ if f > 0i32 {
+ ((half + f) >> before as uint) as int
+ } else {
+ -((half - f) >> before as uint) as int
+ }
+}
+
+/* Generate a 32-bit TrueType tag from its 4 characters */
+pub fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 {
+ let a = a as u32;
+ let b = b as u32;
+ let c = c as u32;
+ let d = d as u32;
+ (a << 24 | b << 16 | c << 8 | d) as u32
+}
+
+#[test]
+fn test_true_type_tag() {
+ assert_eq!(true_type_tag('c', 'm', 'a', 'p'), 0x_63_6D_61_70_u32);
+}
+
+#[test]
+fn test_transform_compress_none() {
+ let test_strs = vec!(
+ " foo bar",
+ "foo bar ",
+ "foo\n bar",
+ "foo \nbar",
+ " foo bar \nbaz",
+ "foo bar baz",
+ "foobarbaz\n\n"
+ );
+ let mode = CompressNone;
+
+ for test in test_strs.iter() {
+ let mut new_line_pos = vec!();
+ let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos);
+ assert_eq!(trimmed_str.as_slice(), *test)
+ }
+}
+
+#[test]
+fn test_transform_discard_newline() {
+ let test_strs = vec!(
+ " foo bar",
+ "foo bar ",
+ "foo\n bar",
+ "foo \nbar",
+ " foo bar \nbaz",
+ "foo bar baz",
+ "foobarbaz\n\n"
+ );
+
+ let oracle_strs = vec!(
+ " foo bar",
+ "foo bar ",
+ "foo bar",
+ "foo bar",
+ " foo bar baz",
+ "foo bar baz",
+ "foobarbaz"
+ );
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = DiscardNewline;
+
+ for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) {
+ let mut new_line_pos = vec!();
+ let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos);
+ assert_eq!(trimmed_str.as_slice(), *oracle)
+ }
+}
+
+/* FIXME: Fix and re-enable
+#[test]
+fn test_transform_compress_whitespace() {
+ let test_strs : ~[String] = ~[" foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo\n bar".to_string(),
+ "foo \nbar".to_string(),
+ " foo bar \nbaz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz\n\n".to_string()];
+
+ let oracle_strs : ~[String] = ~[" foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo\n bar".to_string(),
+ "foo \nbar".to_string(),
+ " foo bar \nbaz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz\n\n".to_string()];
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = CompressWhitespace;
+
+ for i in range(0, test_strs.len()) {
+ let mut new_line_pos = ~[];
+ let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos);
+ assert_eq!(&trimmed_str, &oracle_strs[i])
+ }
+}
+
+#[test]
+fn test_transform_compress_whitespace_newline() {
+ let test_strs : ~[String] = ~[" foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo\n bar".to_string(),
+ "foo \nbar".to_string(),
+ " foo bar \nbaz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz\n\n".to_string()];
+
+ let oracle_strs : ~[String] = ~["foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo bar".to_string(),
+ "foo bar".to_string(),
+ " foo bar baz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz ".to_string()];
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = CompressWhitespaceNewline;
+
+ for i in range(0, test_strs.len()) {
+ let mut new_line_pos = ~[];
+ let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos);
+ assert_eq!(&trimmed_str, &oracle_strs[i])
+ }
+}
+*/
+
+#[test]
+fn test_transform_compress_whitespace_newline_no_incoming() {
+ let test_strs = vec!(
+ " foo bar",
+ "\nfoo bar",
+ "foo bar ",
+ "foo\n bar",
+ "foo \nbar",
+ " foo bar \nbaz",
+ "foo bar baz",
+ "foobarbaz\n\n"
+ );
+
+ let oracle_strs = vec!(
+ " foo bar",
+ " foo bar",
+ "foo bar ",
+ "foo bar",
+ "foo bar",
+ " foo bar baz",
+ "foo bar baz",
+ "foobarbaz "
+ );
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = CompressWhitespaceNewline;
+
+ for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) {
+ let mut new_line_pos = vec!();
+ let (trimmed_str, _out) = transform_text(*test, mode, false, &mut new_line_pos);
+ assert_eq!(trimmed_str.as_slice(), *oracle)
+ }
+}