Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text: Fix getCharBoundaries() for 0-width characters #18860

Merged
merged 4 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/avm2/globals/flash/text/text_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1784,6 +1784,10 @@ pub fn get_char_boundaries<'gc>(
return Ok(Value::Null);
};

if bounds.width() == swf::Twips::ZERO {
return Ok(Value::Null);
}

let rect = activation
.avm2()
.classes()
Expand Down
20 changes: 20 additions & 0 deletions core/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,9 @@ impl<'gc> Font<'gc> {
/// of transforms and glyphs which will be consumed by the `glyph_func`
/// closure. This corresponds to the series of drawing operations necessary
/// to render the text on a single horizontal line.
///
/// It's guaranteed that this function will iterate over all characters
/// from the text, irrespectively of whether they have a glyph or not.
pub fn evaluate<FGlyph>(
&self,
text: &WStr, // TODO: take an `IntoIterator<Item=char>`, to not depend on string representation?
Expand All @@ -606,6 +609,9 @@ impl<'gc> Font<'gc> {

transform.matrix.a = scale;
transform.matrix.d = scale;

// TODO [KJ] I'm not sure whether we should iterate over characters here or over code units.
// I suspect Flash Player does not support full UTF-16 when displaying and laying out text.
let mut char_indices = text.char_indices().peekable();
let has_kerning_info = self.has_kerning_info();
let mut x = Twips::ZERO;
Expand Down Expand Up @@ -637,6 +643,10 @@ impl<'gc> Font<'gc> {
// Step horizontally.
transform.matrix.tx += twips_advance;
x += twips_advance;
} else {
// No glyph, zero advance. This makes it possible to use this method for purposes
// other than rendering the font, e.g. measurement, iterating over characters.
glyph_func(pos, &transform, &Glyph::empty(c), Twips::ZERO, x);
}
}
}
Expand Down Expand Up @@ -818,6 +828,16 @@ pub struct Glyph {
}

impl Glyph {
/// Returns an empty glyph with zero advance.
pub fn empty(character: char) -> Self {
Self {
shape_handle: Default::default(),
shape: GlyphShape::None,
advance: Twips::ZERO,
character,
}
}

pub fn shape_handle(&self, renderer: &mut dyn RenderBackend) -> Option<ShapeHandle> {
self.shape_handle
.borrow_mut()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;

[SWF(width="400", height="400")]
public class Test extends Sprite {
private var text:TextField;

public function Test() {
stage.scaleMode = "noScale";
text = new TextField();
text.border = true;
text.x = 10;
text.y = 10;
text.width = 380;
text.height = 380;
text.multiline = true;
text.embedFonts = true;
addChild(text);

testHtml("<font face='Unknown Font'>x y</font>");
}

private function testHtml(htmlText:String):void {
text.htmlText = htmlText;
trace("Text: " + htmlText.replace(/[\r\n]/g, "\\n"));

testAt(-5);
testAt(-1);
for (var i = 0; i <= text.text.length; ++i) {
testAt(i);
}
}

private function testAt(charIndex:int):void {
var bounds = text.getCharBoundaries(charIndex);
trace(" text.getCharBoundaries(" + charIndex + ") = " + bounds);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Text: <font face='Unknown Font'>x y</font>
text.getCharBoundaries(-5) = null
text.getCharBoundaries(-1) = null
text.getCharBoundaries(0) = null
text.getCharBoundaries(1) = null
text.getCharBoundaries(2) = null
text.getCharBoundaries(3) = null
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
num_ticks = 1

# Currently Ruffle falls back to device font on missing embedded font.
known_failure = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;

[SWF(width="400", height="400")]
public class Test extends Sprite {
[Embed(source="TestFont.ttf", fontName="TestFont", embedAsCFF="false", unicodeRange="U+0061-U+0066")]
private var TestFont:Class;

private var text:TextField;

public function Test() {
stage.scaleMode = "noScale";
text = new TextField();
text.border = true;
text.x = 10;
text.y = 10;
text.width = 380;
text.height = 380;
text.multiline = true;
text.embedFonts = true;
addChild(text);

testHtml("<font face='TestFont'>x y</font>");
testHtml("<font face='TestFont'>abcd ab</font>");

text.wordWrap = true;
testHtml("<p align='justify'><font size='30' face='TestFont'>ab ab abababababa</font></p>");
testHtml("<p align='justify'><font size='30' face='TestFont'>ab ab abababababab</font></p>");
}

private function testHtml(htmlText:String):void {
text.htmlText = htmlText;
trace("Text: " + htmlText.replace(/[\r\n]/g, "\\n"));

testAt(-5);
testAt(-1);
for (var i = 0; i <= text.text.length; ++i) {
testAt(i);
}
}

private function testAt(charIndex:int):void {
var bounds = text.getCharBoundaries(charIndex);
trace(" text.getCharBoundaries(" + charIndex + ") = " + bounds);
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
Text: <font face='TestFont'>x y</font>
text.getCharBoundaries(-5) = null
text.getCharBoundaries(-1) = null
text.getCharBoundaries(0) = null
text.getCharBoundaries(1) = null
text.getCharBoundaries(2) = null
text.getCharBoundaries(3) = null
Text: <font face='TestFont'>abcd ab</font>
text.getCharBoundaries(-5) = null
text.getCharBoundaries(-1) = null
text.getCharBoundaries(0) = (x=2, y=2, w=9.6, h=12)
text.getCharBoundaries(1) = (x=11.6, y=2, w=9.6, h=12)
text.getCharBoundaries(2) = null
text.getCharBoundaries(3) = null
text.getCharBoundaries(4) = null
text.getCharBoundaries(5) = (x=21.2, y=2, w=9.6, h=12)
text.getCharBoundaries(6) = (x=30.8, y=2, w=9.6, h=12)
text.getCharBoundaries(7) = null
Text: <p align='justify'><font size='30' face='TestFont'>ab ab abababababa</font></p>
text.getCharBoundaries(-5) = null
text.getCharBoundaries(-1) = null
text.getCharBoundaries(0) = (x=2, y=2, w=24, h=30)
text.getCharBoundaries(1) = (x=26, y=2, w=24, h=30)
text.getCharBoundaries(2) = null
text.getCharBoundaries(3) = (x=50, y=2, w=24, h=30)
text.getCharBoundaries(4) = (x=74, y=2, w=24, h=30)
text.getCharBoundaries(5) = null
text.getCharBoundaries(6) = (x=98, y=2, w=24, h=30)
text.getCharBoundaries(7) = (x=122, y=2, w=24, h=30)
text.getCharBoundaries(8) = (x=146, y=2, w=24, h=30)
text.getCharBoundaries(9) = (x=170, y=2, w=24, h=30)
text.getCharBoundaries(10) = (x=194, y=2, w=24, h=30)
text.getCharBoundaries(11) = (x=218, y=2, w=24, h=30)
text.getCharBoundaries(12) = (x=242, y=2, w=24, h=30)
text.getCharBoundaries(13) = (x=266, y=2, w=24, h=30)
text.getCharBoundaries(14) = (x=290, y=2, w=24, h=30)
text.getCharBoundaries(15) = (x=314, y=2, w=24, h=30)
text.getCharBoundaries(16) = (x=338, y=2, w=24, h=30)
text.getCharBoundaries(17) = null
text.getCharBoundaries(18) = null
Text: <p align='justify'><font size='30' face='TestFont'>ab ab abababababab</font></p>
text.getCharBoundaries(-5) = null
text.getCharBoundaries(-1) = null
text.getCharBoundaries(0) = (x=2, y=2, w=24, h=30)
text.getCharBoundaries(1) = (x=26, y=2, w=24, h=30)
text.getCharBoundaries(2) = (x=50, y=2, w=280, h=30)
text.getCharBoundaries(3) = (x=330, y=2, w=24, h=30)
text.getCharBoundaries(4) = (x=354, y=2, w=24, h=30)
text.getCharBoundaries(5) = null
text.getCharBoundaries(6) = (x=2, y=32, w=24, h=30)
text.getCharBoundaries(7) = (x=26, y=32, w=24, h=30)
text.getCharBoundaries(8) = (x=50, y=32, w=24, h=30)
text.getCharBoundaries(9) = (x=74, y=32, w=24, h=30)
text.getCharBoundaries(10) = (x=98, y=32, w=24, h=30)
text.getCharBoundaries(11) = (x=122, y=32, w=24, h=30)
text.getCharBoundaries(12) = (x=146, y=32, w=24, h=30)
text.getCharBoundaries(13) = (x=170, y=32, w=24, h=30)
text.getCharBoundaries(14) = (x=194, y=32, w=24, h=30)
text.getCharBoundaries(15) = (x=218, y=32, w=24, h=30)
text.getCharBoundaries(16) = (x=242, y=32, w=24, h=30)
text.getCharBoundaries(17) = (x=266, y=32, w=24, h=30)
text.getCharBoundaries(18) = null
text.getCharBoundaries(19) = null
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_ticks = 1