airsdk / Adobe-Runtime-Support

Report, track and discuss issues in Adobe AIR. Monitored by Adobe - and HARMAN - and maintained by the AIR community.
206 stars 11 forks source link

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

Open itlancer opened 6 months ago

itlancer commented 6 months ago

Problem Description

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

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 commented 1 month ago

Issue still exists with latest AIR 51.1.1.5.