1+ using System . Drawing ;
2+ using System . Drawing . Drawing2D ;
3+ using System . Drawing . Imaging ;
4+ using System . Drawing . Text ;
15using System . Globalization ;
26
37namespace MiniSoftware ;
@@ -1525,7 +1529,7 @@ float RenderPrintTitleRows()
15251529 var tw = ( float ) MeasureHelveticaWidth ( titleCellLines [ i ] [ lineIdx ] , cellFs , bold : titleIsBold ) ;
15261530 textX = titleContentX + ( titleContentWidth - tw ) / 2f ;
15271531 }
1528- currentPage ! . AddText ( titleCellLines [ i ] [ lineIdx ] , textX , cellY , cellFs , cell ? . Color ,
1532+ AddExcelText ( currentPage ! , titleCellLines [ i ] [ lineIdx ] , textX , cellY , cellFs , cell ? . Color ,
15291533 maxWidth : lineMaxWidth ,
15301534 bold : titleIsBold ,
15311535 underline : cell ? . Underline ?? false ,
@@ -1945,7 +1949,7 @@ float RenderPrintTitleRows()
19451949 var tw = ( float ) MeasureHelveticaWidth ( lines [ lineIdx ] , options . FontSize , bold : mpIsBold ) ;
19461950 textX = mpContentX + ( mpContentWidth - tw ) / 2f ;
19471951 }
1948- currentPage ! . AddText ( lines [ lineIdx ] , textX , cellY , options . FontSize , color ,
1952+ AddExcelText ( currentPage ! , lines [ lineIdx ] , textX , cellY , options . FontSize , color ,
19491953 bold : mpIsBold ,
19501954 underline : cell ? . Underline ?? false ,
19511955 strikethrough : cell ? . Strikethrough ?? false ,
@@ -2176,7 +2180,7 @@ float RenderPrintTitleRows()
21762180 // overlap with the normal text.
21772181 var boldWidth = ( float ) MeasureFontWidth ( boldPart , cellFontSize , bold : true , cell ? . FontName ) ;
21782182
2179- currentPage ! . AddText ( boldPart , textX , cellY , cellFontSize , color ,
2183+ AddExcelText ( currentPage ! , boldPart , textX , cellY , cellFontSize , color ,
21802184 maxWidth : boldWidth ,
21812185 bold : true ,
21822186 underline : cell ? . Underline ?? false ,
@@ -2187,7 +2191,7 @@ float RenderPrintTitleRows()
21872191 {
21882192 var normalX = textX + boldWidth + spaceGap ;
21892193 var normalMax = lineMaxWidth > 0 ? lineMaxWidth - boldWidth - spaceGap : 0f ;
2190- currentPage ! . AddText ( normalPart , normalX , cellY , cellFontSize , color ,
2194+ AddExcelText ( currentPage ! , normalPart , normalX , cellY , cellFontSize , color ,
21912195 maxWidth : normalMax > 0 ? normalMax : 0f ,
21922196 bold : false ,
21932197 underline : cell ? . Underline ?? false ,
@@ -2198,7 +2202,7 @@ float RenderPrintTitleRows()
21982202 }
21992203 else
22002204 {
2201- currentPage ! . AddText ( lines [ lineIdx ] , textX , cellY , cellFontSize , color ,
2205+ AddExcelText ( currentPage ! , lines [ lineIdx ] , textX , cellY , cellFontSize , color ,
22022206 maxWidth : lineMaxWidth ,
22032207 bold : isBold ,
22042208 underline : cell ? . Underline ?? false ,
@@ -3693,6 +3697,121 @@ private static string[] WrapCellText(string text, float widthPts, float fontSize
36933697 return lines . ToArray ( ) ;
36943698 }
36953699
3700+ private static void AddExcelText ( PdfPage page , string text , float x , float y , float fontSize , PdfColor ? color = null , ( float , float , float , float ) ? clipRect = null , float ? maxWidth = null , bool bold = false , bool italic = false , bool underline = false , float charSpacing = 0 , float wordSpacing = 0 , string ? preferredFontName = null , float ? underlineWidth = null , bool strikethrough = false )
3701+ {
3702+ if ( ! ContainsIndicText ( text ) || ! TryRenderComplexScriptText ( text , fontSize , color ?? PdfColor . Black , bold , italic , preferredFontName , maxWidth , out var png , out var widthPt , out var heightPt , out var baselineFromBottomPt ) )
3703+ {
3704+ page . AddText ( text , x , y , fontSize , color , clipRect , maxWidth , bold , italic , underline , charSpacing , wordSpacing , preferredFontName , underlineWidth , strikethrough ) ;
3705+ return ;
3706+ }
3707+
3708+ var imageY = y - baselineFromBottomPt ;
3709+ page . AddImage ( png , "png" , x , imageY , widthPt , heightPt ) ;
3710+ page . AddText ( text , x , y , fontSize , color , clipRect , maxWidth , bold , italic , underline : false , charSpacing , wordSpacing , preferredFontName , underlineWidth : null , strikethrough : false , hidden : true ) ;
3711+ }
3712+
3713+ private static bool ContainsIndicText ( string text )
3714+ {
3715+ foreach ( var ch in text )
3716+ {
3717+ if ( ch is >= '\u0900 ' and <= '\u0D7F ' )
3718+ return true ;
3719+ }
3720+ return false ;
3721+ }
3722+
3723+ private static bool TryRenderComplexScriptText ( string text , float fontSize , PdfColor pdfColor , bool bold , bool italic , string ? preferredFontName , float ? maxWidth , out byte [ ] png , out float widthPt , out float heightPt , out float baselineFromBottomPt )
3724+ {
3725+ png = [ ] ;
3726+ widthPt = 0 ;
3727+ heightPt = 0 ;
3728+ baselineFromBottomPt = 0 ;
3729+
3730+ try
3731+ {
3732+ const float dpiScale = 3f ;
3733+ var fontStyle = ( bold ? FontStyle . Bold : FontStyle . Regular ) | ( italic ? FontStyle . Italic : FontStyle . Regular ) ;
3734+ using var font = CreateComplexScriptFont ( preferredFontName , fontSize , fontStyle ) ;
3735+ using var probe = new Bitmap ( 1 , 1 , PixelFormat . Format32bppArgb ) ;
3736+ probe . SetResolution ( 72f * dpiScale , 72f * dpiScale ) ;
3737+ using var probeGraphics = Graphics . FromImage ( probe ) ;
3738+ probeGraphics . TextRenderingHint = TextRenderingHint . AntiAliasGridFit ;
3739+ var format = StringFormat . GenericTypographic ;
3740+ format . FormatFlags |= StringFormatFlags . MeasureTrailingSpaces | StringFormatFlags . NoClip ;
3741+ var measured = probeGraphics . MeasureString ( text , font , int . MaxValue , format ) ;
3742+
3743+ var naturalWidthPt = Math . Max ( 1f , measured . Width / dpiScale ) ;
3744+ widthPt = Math . Max ( 1f , Math . Min ( maxWidth ?? naturalWidthPt , naturalWidthPt ) ) ;
3745+
3746+ var family = font . FontFamily ;
3747+ var em = family . GetEmHeight ( font . Style ) ;
3748+ var ascent = family . GetCellAscent ( font . Style ) ;
3749+ var lineSpacing = family . GetLineSpacing ( font . Style ) ;
3750+ var baselineFromTopPt = fontSize * ascent / em ;
3751+ heightPt = Math . Max ( fontSize * 1.35f , fontSize * lineSpacing / em ) ;
3752+ baselineFromBottomPt = Math . Max ( 0.1f , heightPt - baselineFromTopPt ) ;
3753+
3754+ var widthPx = Math . Max ( 1 , ( int ) Math . Ceiling ( widthPt * dpiScale ) ) ;
3755+ var heightPx = Math . Max ( 1 , ( int ) Math . Ceiling ( heightPt * dpiScale ) ) ;
3756+ using var bmp = new Bitmap ( widthPx , heightPx , PixelFormat . Format32bppArgb ) ;
3757+ bmp . SetResolution ( 72f * dpiScale , 72f * dpiScale ) ;
3758+ using ( var graphics = Graphics . FromImage ( bmp ) )
3759+ {
3760+ graphics . Clear ( System . Drawing . Color . Transparent ) ;
3761+ graphics . SmoothingMode = SmoothingMode . AntiAlias ;
3762+ graphics . TextRenderingHint = TextRenderingHint . AntiAliasGridFit ;
3763+ graphics . PixelOffsetMode = PixelOffsetMode . HighQuality ;
3764+ using var brush = new SolidBrush ( ToDrawingColor ( pdfColor ) ) ;
3765+ graphics . SetClip ( new RectangleF ( 0 , 0 , widthPx , heightPx ) ) ;
3766+ graphics . DrawString ( text , font , brush , 0 , 0 , format ) ;
3767+ }
3768+
3769+ using var ms = new MemoryStream ( ) ;
3770+ bmp . Save ( ms , ImageFormat . Png ) ;
3771+ png = ms . ToArray ( ) ;
3772+ return png . Length > 0 ;
3773+ }
3774+ catch
3775+ {
3776+ return false ;
3777+ }
3778+ }
3779+
3780+ private static Font CreateComplexScriptFont ( string ? preferredFontName , float fontSize , FontStyle style )
3781+ {
3782+ foreach ( var name in ComplexScriptFontCandidates ( preferredFontName ) )
3783+ {
3784+ try
3785+ {
3786+ if ( FontFamily . Families . Any ( f => string . Equals ( f . Name , name , StringComparison . OrdinalIgnoreCase ) ) )
3787+ return new Font ( name , fontSize , style , GraphicsUnit . Point ) ;
3788+ }
3789+ catch
3790+ {
3791+ }
3792+ }
3793+
3794+ return new Font ( FontFamily . GenericSansSerif , fontSize , style , GraphicsUnit . Point ) ;
3795+ }
3796+
3797+ private static IEnumerable < string > ComplexScriptFontCandidates ( string ? preferredFontName )
3798+ {
3799+ if ( ! string . IsNullOrWhiteSpace ( preferredFontName ) )
3800+ yield return preferredFontName ! ;
3801+ yield return "Nirmala UI" ;
3802+ yield return "Mangal" ;
3803+ yield return "Latha" ;
3804+ yield return "Vrinda" ;
3805+ yield return "Gautami" ;
3806+ yield return "Shruti" ;
3807+ }
3808+
3809+ private static System . Drawing . Color ToDrawingColor ( PdfColor color )
3810+ {
3811+ static int Component ( float value ) => Math . Max ( 0 , Math . Min ( 255 , ( int ) Math . Round ( value * 255f ) ) ) ;
3812+ return System . Drawing . Color . FromArgb ( 255 , Component ( color . R ) , Component ( color . G ) , Component ( color . B ) ) ;
3813+ }
3814+
36963815 /// <summary>
36973816 /// Checks if a sheet name is a generic default like Sheet1, Sheet2, etc.
36983817 /// </summary>
0 commit comments