Adobe-Runtime-Support icon indicating copy to clipboard operation
Adobe-Runtime-Support copied to clipboard

[Windows] Non-embedded fonts displayed without anti-aliasing (smoothing)

Open itlancer opened this issue 4 months ago • 0 comments

Problem Description

Non-embedded fonts displayed without anti-aliasing (smoothing) for Windows devices. Embedded fonts looks better better. I tried multiple approaches - nothing helps:

  • TextRenderer.setAdvancedAntiAliasingTable() with CSMSettings
  • TextRenderer.displayMode and TextRenderer.maxLevel
  • Rasterize (draw to BitmapData)
  • cacheAsBitmapData
  • TextField::antiAliasType
  • TextLine

Tested with multiple AIR versions, even with latest AIR 50.2.5.1 with multiple Windows devices, with multiple applications, different fonts and OS versions. Same issue in all cases. Most noticeable with FullHD screens (or lower resolution) with screen scaling 100% and low text sizes (15-30). There is no such issue with macOS (tested with the same monitors with the same screen scaling). Didn't test with Linux and other platforms.

Related issues: https://github.com/airsdk/Adobe-Runtime-Support/issues/2744 https://github.com/airsdk/Adobe-Runtime-Support/issues/2201 https://github.com/airsdk/Adobe-Runtime-Support/issues/1957 https://github.com/airsdk/Adobe-Runtime-Support/issues/867 https://github.com/airsdk/Adobe-Runtime-Support/issues/150

Steps to Reproduce

Launch application with code below on any Windows device. Better to use devices with FullHD resolution screen (non-HiDPI) with 100% screen scaling. There is 12 rows with different approaches for comparison:

  1. TextField with embedded font.
  2. TextField with embedded font, AntiAliasType.ADVANCED and GridFitType.NONE.
  3. TextField with non-embedded font.
  4. TextField rasterized to BitmapData.
  5. TextField rasterized to BitmapData with x2 width/height.
  6. TextField with x2 text size rasterized to BitmapData.
  7. TextField with non-embedded font and cacheAsBitmapData.
  8. StageText.
  9. TextLine with non-embedded font.
  10. TextLine with non-embedded font and RenderingMode.CFF.
  11. TextLine with embedded font.
  12. TextField rasterized to BitmapData with x4 width/height.

Application example with sources and comparison screenshot attached. windows_textfield_antialiasing_bug.zip

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.text.Font;
	import flash.text.AntiAliasType;
	import flash.text.GridFitType;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.PixelSnapping;
	import flash.text.StageText;
	import flash.geom.Rectangle;
	import flash.text.engine.FontWeight;
	import flash.text.engine.ElementFormat;
	import flash.text.engine.FontDescription;
	import flash.text.engine.FontPosture;
	import flash.text.engine.FontLookup;
	import flash.text.engine.RenderingMode;
	import flash.text.engine.CFFHinting;
	import flash.text.engine.TextElement;
	import flash.text.engine.TextBlock;
	import flash.text.engine.TextLine;
	import flash.geom.Matrix;
	
	public class WindowsTextFieldAntialiasingBug extends Sprite {
		[Embed(source="Montserrat-Bold.ttf", fontFamily="MontserratBold", embedAsCFF="false")]
		private var MyFont:Class;
		
		[Embed(source="Montserrat-Bold.ttf", fontFamily="MontserratBoldCFF", embedAsCFF="true")]
		private var MyFontCFF:Class;
		
		private const TEXT:String = "Test Test";
		private const FONT_NAME:String = "Montserrat";
		private const EMBEDDED_FONT_NAME:String = "MontserratBold";
		private const EMBEDDED_FONT_NAME_CFF:String = "MontserratBoldCFF";
		private const TEXT_COLOR:uint = 0xffffff;
		private const TEXT_SIZE:uint = 20;
		
		public function WindowsTextFieldAntialiasingBug() {
			addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			//Row 1, TextField with embedded font
			var embeddedBoldTextField:TextField = new TextField();
			embeddedBoldTextField.x = 10;
			embeddedBoldTextField.autoSize = TextFieldAutoSize.LEFT;
			embeddedBoldTextField.embedFonts = true;
			var embeddedBoldTextFormat:TextFormat = new TextFormat(EMBEDDED_FONT_NAME);
			embeddedBoldTextFormat.size = TEXT_SIZE;
			embeddedBoldTextFormat.bold = true;
			embeddedBoldTextFormat.color = TEXT_COLOR;
			embeddedBoldTextField.defaultTextFormat = embeddedBoldTextFormat;
			embeddedBoldTextField.text = TEXT;
			addChild(embeddedBoldTextField);
			
			//Row 2, TextField with embedded font, advanced
			var embeddedBoldAdvancedTextField:TextField = new TextField();
			embeddedBoldAdvancedTextField.x = 10;
			embeddedBoldAdvancedTextField.y = 50;
			embeddedBoldAdvancedTextField.autoSize = TextFieldAutoSize.LEFT;
			embeddedBoldAdvancedTextField.embedFonts = true;
			var embeddedBoldAdvancedTextFormat:TextFormat = new TextFormat(EMBEDDED_FONT_NAME);
			embeddedBoldAdvancedTextFormat.size = TEXT_SIZE;
			embeddedBoldAdvancedTextFormat.bold = true;
			embeddedBoldAdvancedTextFormat.color = TEXT_COLOR;
			embeddedBoldAdvancedTextField.antiAliasType = AntiAliasType.ADVANCED;
			embeddedBoldAdvancedTextField.gridFitType = GridFitType.NONE;
			embeddedBoldAdvancedTextField.defaultTextFormat = embeddedBoldAdvancedTextFormat;
			embeddedBoldAdvancedTextField.text = TEXT;
			addChild(embeddedBoldAdvancedTextField);
			
			//Row 3, TextField with non-embedded font
			//This one looks ugly for Windows
			var nonEmbeddedBoldTextField:TextField = new TextField();
			nonEmbeddedBoldTextField.x = 10;
			nonEmbeddedBoldTextField.y = 100;
			nonEmbeddedBoldTextField.autoSize = TextFieldAutoSize.LEFT;
			var nonEmbeddedBoldTextFormat:TextFormat = new TextFormat(FONT_NAME);
			nonEmbeddedBoldTextFormat.size = TEXT_SIZE;
			nonEmbeddedBoldTextFormat.bold = true;
			nonEmbeddedBoldTextFormat.color = TEXT_COLOR;
			nonEmbeddedBoldTextField.defaultTextFormat = nonEmbeddedBoldTextFormat;
			nonEmbeddedBoldTextField.text = TEXT;
			addChild(nonEmbeddedBoldTextField);
			
			//Row 4, BitmapData
			var nonEmbeddedBoldBitmapData:BitmapData = new BitmapData(nonEmbeddedBoldTextField.textWidth, nonEmbeddedBoldTextField.textHeight, true, 0x00000000);
			nonEmbeddedBoldBitmapData.draw(nonEmbeddedBoldTextField, null, null, null, null, true);
			var nonEmbeddedBoldBitmap:Bitmap = new Bitmap(nonEmbeddedBoldBitmapData, PixelSnapping.AUTO, true);
			nonEmbeddedBoldBitmap.x = 10;
			nonEmbeddedBoldBitmap.y = 150;
			addChild(nonEmbeddedBoldBitmap);
			
			//Row 5, BitmapData double BD
			var nonEmbeddedBoldDoubleBitmapData:BitmapData = new BitmapData(nonEmbeddedBoldTextField.textWidth * 2, nonEmbeddedBoldTextField.textHeight * 2, true, 0x00000000);
			var nonEmbeddedBoldDoubleMatrix:Matrix = new Matrix();
			nonEmbeddedBoldDoubleMatrix.identity();
			nonEmbeddedBoldDoubleMatrix.scale(2, 2);
			nonEmbeddedBoldDoubleBitmapData.draw(nonEmbeddedBoldTextField, nonEmbeddedBoldDoubleMatrix, null, null, null, true);
			var nonEmbeddedBoldDoubleBitmap:Bitmap = new Bitmap(nonEmbeddedBoldDoubleBitmapData, PixelSnapping.AUTO, true);
			nonEmbeddedBoldDoubleBitmap.width = nonEmbeddedBoldTextField.textWidth;
			nonEmbeddedBoldDoubleBitmap.height = nonEmbeddedBoldTextField.textHeight;
			nonEmbeddedBoldDoubleBitmap.x = 10;
			nonEmbeddedBoldDoubleBitmap.y = 200;
			addChild(nonEmbeddedBoldDoubleBitmap);
			
			//Row 6, BitmapData double size
			var nonEmbeddedBoldDouble2BitmapData:BitmapData = new BitmapData(nonEmbeddedBoldTextField.textWidth, nonEmbeddedBoldTextField.textHeight, true, 0x00000000);
			var tf:TextFormat = nonEmbeddedBoldTextField.defaultTextFormat;
			tf.size = TEXT_SIZE * 2;
			nonEmbeddedBoldTextField.defaultTextFormat = tf;
			nonEmbeddedBoldTextField.setTextFormat(tf);
			var nonEmbeddedBoldDouble2Matrix:Matrix = new Matrix();
			nonEmbeddedBoldDouble2Matrix.identity();
			nonEmbeddedBoldDouble2Matrix.scale(0.5, 0.5);
			nonEmbeddedBoldDouble2BitmapData.draw(nonEmbeddedBoldTextField, nonEmbeddedBoldDouble2Matrix, null, null, null, true);
			var nonEmbeddedBoldDouble2Bitmap:Bitmap = new Bitmap(nonEmbeddedBoldDouble2BitmapData, PixelSnapping.AUTO, true);
			nonEmbeddedBoldDouble2Bitmap.x = 10;
			nonEmbeddedBoldDouble2Bitmap.y = 250;
			addChild(nonEmbeddedBoldDouble2Bitmap);
			
			//reset TextFormat back
			tf = nonEmbeddedBoldTextField.defaultTextFormat;
			tf.size = TEXT_SIZE;
			nonEmbeddedBoldTextField.defaultTextFormat = tf;
			nonEmbeddedBoldTextField.setTextFormat(tf);
			
			
			//Row 7, TextField with non-embedded font, cached
			var nonEmbeddedBoldCacheTextField:TextField = new TextField();
			nonEmbeddedBoldCacheTextField.x = 10;
			nonEmbeddedBoldCacheTextField.y = 300;
			nonEmbeddedBoldCacheTextField.autoSize = TextFieldAutoSize.LEFT;
			nonEmbeddedBoldCacheTextField.cacheAsBitmap = true;
			var nonEmbeddedBoldCacheTextFormat:TextFormat = new TextFormat(FONT_NAME);
			nonEmbeddedBoldCacheTextFormat.size = TEXT_SIZE;
			nonEmbeddedBoldCacheTextFormat.bold = true;
			nonEmbeddedBoldCacheTextFormat.color = TEXT_COLOR;
			nonEmbeddedBoldCacheTextField.defaultTextFormat = nonEmbeddedBoldCacheTextFormat;
			nonEmbeddedBoldCacheTextField.text = TEXT;
			addChild(nonEmbeddedBoldCacheTextField);
			
			
			//Row 8, StageText
			var stageTextBold:StageText = new StageText();
			stageTextBold.fontSize = TEXT_SIZE;
			stageTextBold.fontFamily = FONT_NAME;
			stageTextBold.fontWeight = FontWeight.BOLD;
			stageTextBold.color = TEXT_COLOR;
			stageTextBold.text = TEXT;
			stageTextBold.viewPort = new Rectangle(10, 350, 155, 50);
			stageTextBold.stage = stage;
			stageTextBold.visible = true;
			
			//Row 9, TextLine
			var boldFontDescription:FontDescription = new FontDescription(FONT_NAME, FontWeight.BOLD, FontPosture.NORMAL, FontLookup.DEVICE, RenderingMode.NORMAL, CFFHinting.HORIZONTAL_STEM);
			var boldElementFormat:ElementFormat = new ElementFormat(boldFontDescription);
			boldElementFormat.fontSize = TEXT_SIZE;
			boldElementFormat.fontDescription = boldFontDescription;
			boldElementFormat.color = TEXT_COLOR;
			var boldTextElement:TextElement = new TextElement(TEXT, boldElementFormat);
			var boldTextBlock:TextBlock = new TextBlock();
			boldTextBlock.content = boldTextElement;
			var boldTextLine:TextLine = boldTextBlock.createTextLine(null, 150);
			boldTextLine.x = 10;
			boldTextLine.y = 400 + boldTextLine.ascent + boldTextLine.descent;
			addChild(boldTextLine);
			
			//Column 10, TextLine CFF
			var boldCFFFontDescription:FontDescription = new FontDescription(FONT_NAME, FontWeight.BOLD, FontPosture.NORMAL, FontLookup.DEVICE, RenderingMode.CFF, CFFHinting.HORIZONTAL_STEM);
			var boldCFFElementFormat:ElementFormat = new ElementFormat(boldCFFFontDescription);
			boldCFFElementFormat.fontSize = TEXT_SIZE;
			boldCFFElementFormat.fontDescription = boldCFFFontDescription;
			boldCFFElementFormat.color = TEXT_COLOR;
			var boldCFFTextElement:TextElement = new TextElement(TEXT, boldCFFElementFormat);
			var boldCFFTextBlock:TextBlock = new TextBlock();
			boldCFFTextBlock.content = boldCFFTextElement;
			var boldCFFTextLine:TextLine = boldCFFTextBlock.createTextLine(null, 150);
			boldCFFTextLine.x = 10;
			boldCFFTextLine.y = 450 + boldCFFTextLine.ascent + boldCFFTextLine.descent;
			addChild(boldCFFTextLine);
			
			//Column 11, TextLine CFF
			var boldEmbeddedCFFFontDescription:FontDescription = new FontDescription(EMBEDDED_FONT_NAME_CFF, FontWeight.BOLD, FontPosture.NORMAL, FontLookup.EMBEDDED_CFF, RenderingMode.CFF, CFFHinting.HORIZONTAL_STEM);
			var boldEmbeddedCFFElementFormat:ElementFormat = new ElementFormat(boldEmbeddedCFFFontDescription);
			boldEmbeddedCFFElementFormat.fontSize = TEXT_SIZE;
			boldEmbeddedCFFElementFormat.fontDescription = boldEmbeddedCFFFontDescription;
			boldEmbeddedCFFElementFormat.color = TEXT_COLOR;
			var boldEmbeddedCFFTextElement:TextElement = new TextElement(TEXT, boldEmbeddedCFFElementFormat);
			var boldEmbeddedCFFTextBlock:TextBlock = new TextBlock();
			boldEmbeddedCFFTextBlock.content = boldEmbeddedCFFTextElement;
			var boldEmbeddedCFFTextLine:TextLine = boldEmbeddedCFFTextBlock.createTextLine(null, 150);
			boldEmbeddedCFFTextLine.x = 10;
			boldEmbeddedCFFTextLine.y = 500 + boldEmbeddedCFFTextLine.ascent + boldEmbeddedCFFTextLine.descent;
			addChild(boldEmbeddedCFFTextLine);
			
			//Row 12, BitmapData x4
			var nonEmbeddedBold4BitmapData:BitmapData = new BitmapData(nonEmbeddedBoldTextField.textWidth * 4, nonEmbeddedBoldTextField.textHeight * 4, true, 0x00000000);
			var nonEmbeddedBold4Matrix:Matrix = new Matrix();
			nonEmbeddedBold4Matrix.identity();
			nonEmbeddedBold4Matrix.scale(4, 4);
			nonEmbeddedBold4BitmapData.draw(nonEmbeddedBoldTextField, nonEmbeddedBold4Matrix, null, null, null, true);
			var nonEmbeddedBold4Bitmap:Bitmap = new Bitmap(nonEmbeddedBold4BitmapData, PixelSnapping.AUTO, true);
			nonEmbeddedBold4Bitmap.width = nonEmbeddedBoldTextField.textWidth;
			nonEmbeddedBold4Bitmap.height = nonEmbeddedBoldTextField.textHeight;
			nonEmbeddedBold4Bitmap.x = 10;
			nonEmbeddedBold4Bitmap.y = 550;
			addChild(nonEmbeddedBold4Bitmap);
		}
	}
}

Actual Result: All variants with non-embedded fonts looks ugly, without anti-aliasing. Here comparison screenshot. At the left - Windows at the right - macOS. windows_mac Most noticible at row 3: image

  1. Fine. TextField with embedded font.
  2. Best. TextField with embedded font, AntiAliasType.ADVANCED and GridFitType.NONE.
  3. Bad. TextField with non-embedded font.
  4. Bad. TextField rasterized to BitmapData.
  5. Medium. TextField rasterized to BitmapData with x2 width/height.
  6. Bad. TextField with x2 text size rasterized to BitmapData.
  7. Bad. TextField with non-embedded font and cacheAsBitmapData.
  8. Bad. StageText.
  9. Bad. TextLine with non-embedded font.
  10. Bad. TextLine with non-embedded font and RenderingMode.CFF.
  11. Best. TextLine with embedded font.
  12. Fine/Best. TextField rasterized to BitmapData with x4 width/height.

Expected Result: Non-embedded fonts displayed with anti-aliasing like with macOS.

Known Workarounds

Embed fonts if it possible or rasterize with x4 width/height but it consume x16 RAM.

itlancer avatar Apr 20 '24 18:04 itlancer