123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646 |
- using System;
- using System.Collections.Generic;
- using System.Drawing.Drawing2D;
- using System.Drawing;
- using System.Globalization;
- using System.Text;
- using System.Net;
- using System.IO;
- namespace FastReport.Utils
- {
- /// <summary>
- /// Advanced text renderer is used to perform the following tasks:
- /// - draw justified text, text with custom line height, text containing html tags;
- /// - calculate text height, get part of text that does not fit in the display rectangle;
- /// - get paragraphs, lines, words and char sequence to perform accurate export to such
- /// formats as PDF, TXT, RTF
- /// </summary>
- /// <example>Here is how one may operate the renderer items:
- /// <code>
- /// foreach (AdvancedTextRenderer.Paragraph paragraph in renderer.Paragraphs)
- /// {
- /// foreach (AdvancedTextRenderer.Line line in paragraph.Lines)
- /// {
- /// foreach (AdvancedTextRenderer.Word word in line.Words)
- /// {
- /// if (renderer.HtmlTags)
- /// {
- /// foreach (AdvancedTextRenderer.Run run in word.Runs)
- /// {
- /// using (Font f = run.GetFont())
- /// using (Brush b = run.GetBrush())
- /// {
- /// g.DrawString(run.Text, f, b, run.Left, run.Top, renderer.Format);
- /// }
- /// }
- /// }
- /// else
- /// {
- /// g.DrawString(word.Text, renderer.Font, renderer.Brush, word.Left, word.Top, renderer.Format);
- /// }
- /// }
- /// }
- /// }
- /// </code>
- /// </example>
- public class AdvancedTextRenderer
- {
- #region Fields
- private List<Paragraph> paragraphs;
- private string text;
- private IGraphics graphics;
- private Font font;
- private Brush brush;
- private Pen outlinePen;
- private RectangleF displayRect;
- private StringFormat format;
- private HorzAlign horzAlign;
- private VertAlign vertAlign;
- private float lineHeight;
- private float fontLineHeight;
- private int angle;
- private float widthRatio;
- private bool forceJustify;
- private bool wysiwyg;
- private bool htmlTags;
- private bool pDFMode;
- private float spaceWidth;
- private float scale;
- private InlineImageCache cache;
- private float fontScale;
- #endregion
- #region Properties
- public List<Paragraph> Paragraphs
- {
- get { return paragraphs; }
- }
- public IGraphics Graphics
- {
- get { return graphics; }
- }
- public Font Font
- {
- get { return font; }
- }
- public Brush Brush
- {
- get { return brush; }
- }
- public Pen OutlinePen
- {
- get { return outlinePen; }
- }
- public Color BrushColor
- {
- get { return brush is SolidBrush ? (brush as SolidBrush).Color : Color.Black; }
- }
- public RectangleF DisplayRect
- {
- get { return displayRect; }
- }
- public StringFormat Format
- {
- get { return format; }
- }
- public HorzAlign HorzAlign
- {
- get { return horzAlign; }
- }
- public VertAlign VertAlign
- {
- get { return vertAlign; }
- }
- public float LineHeight
- {
- get { return lineHeight; }
- }
- public float FontLineHeight
- {
- get { return fontLineHeight; }
- }
- public int Angle
- {
- get { return angle; }
- }
- public float WidthRatio
- {
- get { return widthRatio; }
- }
- public bool ForceJustify
- {
- get { return forceJustify; }
- }
- public bool Wysiwyg
- {
- get { return wysiwyg; }
- }
- public bool HtmlTags
- {
- get { return htmlTags; }
- }
- public float TabSize
- {
- get
- {
- // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0]
- float firstTab = 0;
- float[] tabSizes = Format.GetTabStops(out firstTab);
- if (tabSizes.Length > 1)
- return tabSizes[1];
- return 0;
- }
- }
- public float TabOffset
- {
- get
- {
- // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0]
- float firstTab = 0;
- float[] tabSizes = Format.GetTabStops(out firstTab);
- if (tabSizes.Length > 0)
- return tabSizes[0];
- return 0;
- }
- }
- public bool WordWrap
- {
- get { return (Format.FormatFlags & StringFormatFlags.NoWrap) == 0; }
- }
- public bool RightToLeft
- {
- get { return (Format.FormatFlags & StringFormatFlags.DirectionRightToLeft) != 0; }
- }
- public bool PDFMode
- {
- get { return pDFMode; }
- }
- internal float SpaceWidth
- {
- get { return spaceWidth; }
- }
- /// <summary>
- /// The scale for font tag
- /// </summary>
- public float FontScale { get { return fontScale; } set { fontScale = value; } }
- public float Scale { get { return scale; } set { scale = value; } }
- public InlineImageCache Cache
- {
- get
- {
- if (cache == null)
- cache = new InlineImageCache();
- return cache;
- }
- }
- #endregion
- #region Private Methods
- const string ab = "abcdefabcdef";
- const string a40b = "abcdef abcdef";
- internal static float CalculateSpaceSize(IGraphics g, Font f)
- {
- float w_ab = g.MeasureString(ab, f).Width;
- float w_a40b = g.MeasureString(a40b, f).Width;
- return (w_a40b - w_ab) / 40;
- }
- private void SplitToParagraphs(string text)
- {
- StyleDescriptor style = new StyleDescriptor(Font.Style, BrushColor, BaseLine.Normal);
- if (HtmlTags)
- text = text.Replace("<br>", "\r\n").Replace("<br/>", "\r\n").Replace("<br />", "\r\n");
- string[] lines = text.Split('\n');
- int originalCharIndex = 0;
- foreach (string line in lines)
- {
- string s = line;
- if (s.Length > 0 && s[s.Length - 1] == '\r')
- s = s.Remove(s.Length - 1);
- Paragraph paragraph = new Paragraph(s, this, originalCharIndex);
- paragraphs.Add(paragraph);
- if (HtmlTags)
- style = paragraph.WrapHtmlLines(style);
- else
- paragraph.WrapLines();
- originalCharIndex += line.Length + 1;
- }
- // skip empty paragraphs at the end
- for (int i = paragraphs.Count - 1; i >= 0; i--)
- {
- if (paragraphs[i].IsEmpty && paragraphs.Count != 1)
- paragraphs.RemoveAt(i);
- else
- break;
- }
- }
- private void AdjustParagraphLines()
- {
- // calculate text height
- float height = CalcHeight();
- // calculate Y offset
- float offsetY = DisplayRect.Top;
- if (VertAlign == VertAlign.Center)
- offsetY += (DisplayRect.Height - height) / 2;
- else if (VertAlign == VertAlign.Bottom)
- offsetY += (DisplayRect.Height - height) - 1;
- for (int i = 0; i < Paragraphs.Count; i++)
- {
- Paragraph paragraph = Paragraphs[i];
- paragraph.AlignLines(i == Paragraphs.Count - 1 && ForceJustify);
- // adjust line tops
- foreach (Line line in paragraph.Lines)
- {
- line.Top = offsetY;
- line.MakeUnderlines();
- offsetY += line.CalcHeight();
- }
- }
- }
- #endregion
- #region Public Methods
- public void Draw()
- {
- // set clipping
- IGraphicsState state = Graphics.Save();
- Graphics.SetClip(DisplayRect, CombineMode.Intersect);
- // reset alignment
- StringAlignment saveAlign = Format.Alignment;
- StringAlignment saveLineAlign = Format.LineAlignment;
- Format.Alignment = StringAlignment.Near;
- Format.LineAlignment = StringAlignment.Near;
- if (Angle != 0)
- {
- Graphics.TranslateTransform(DisplayRect.Left + DisplayRect.Width / 2,
- DisplayRect.Top + DisplayRect.Height / 2);
- Graphics.RotateTransform(Angle);
- }
- Graphics.ScaleTransform(WidthRatio, 1);
- foreach (Paragraph paragraph in Paragraphs)
- {
- paragraph.Draw();
- }
- // restore alignment and clipping
- Format.Alignment = saveAlign;
- Format.LineAlignment = saveLineAlign;
- Graphics.Restore(state);
- }
- public float CalcHeight()
- {
- int charsFit = 0;
- StyleDescriptor style = null;
- return CalcHeight(out charsFit, out style);
- }
- public float CalcHeight(out int charsFit, out StyleDescriptor style)
- {
- charsFit = 0;
- style = null;
- float height = 0;
- float displayHeight = DisplayRect.Height;
- if (LineHeight > displayHeight)
- return 0;
- foreach (Paragraph paragraph in Paragraphs)
- {
- foreach (Line line in paragraph.Lines)
- {
- height += line.CalcHeight();
- if (charsFit == 0 && height > displayHeight)
- {
- charsFit = line.OriginalCharIndex;
- if (HtmlTags)
- style = line.Style;
- }
- }
- }
- if (charsFit == 0)
- charsFit = text.Length;
- return height;
- }
- public float CalcWidth()
- {
- float width = 0;
- foreach (Paragraph paragraph in Paragraphs)
- {
- foreach (Line line in paragraph.Lines)
- {
- if (width < line.Width)
- width = line.Width;
- }
- }
- return width + spaceWidth;
- }
- internal float GetTabPosition(float pos)
- {
- float tabOffset = TabOffset;
- float tabSize = TabSize;
- int tabPosition = (int)((pos - tabOffset) / tabSize);
- if (pos < tabOffset)
- return tabOffset;
- return (tabPosition + 1) * tabSize + tabOffset;
- }
- #endregion
- public AdvancedTextRenderer(string text, IGraphics g, Font font, Brush brush, Pen outlinePen,
- RectangleF rect, StringFormat format, HorzAlign horzAlign, VertAlign vertAlign,
- float lineHeight, int angle, float widthRatio,
- bool forceJustify, bool wysiwyg, bool htmlTags, bool pdfMode,
- float scale, float fontScale, InlineImageCache cache, bool isPrinting = false)
- {
- this.cache = cache;
- this.scale = scale;
- this.fontScale = fontScale;
- paragraphs = new List<Paragraph>();
- this.text = text;
- graphics = g;
- this.font = font;
- this.brush = brush;
- this.outlinePen = outlinePen;
- displayRect = rect;
- this.format = format;
- this.horzAlign = horzAlign;
- this.vertAlign = vertAlign;
- this.lineHeight = lineHeight;
- fontLineHeight = font.GetHeight(g.Graphics);
- if (this.lineHeight == 0)
- {
- this.lineHeight = fontLineHeight;
- if (isPrinting && Config.IsRunningOnMono && DrawUtils.GetMonoRendering(g.Graphics) == MonoRendering.Pango)
- {
- // we need this in order to fix inconsistent line spacing when print using Pango rendering
- this.lineHeight = fontLineHeight * 1.33f;
- }
- }
- this.angle = angle % 360;
- this.widthRatio = widthRatio;
- this.forceJustify = forceJustify;
- this.wysiwyg = wysiwyg;
- this.htmlTags = htmlTags;
- pDFMode = pdfMode;
- spaceWidth = CalculateSpaceSize(g, font);// g.MeasureString(" ", font).Width;
- StringFormatFlags saveFlags = Format.FormatFlags;
- StringTrimming saveTrimming = Format.Trimming;
- // match DrawString behavior:
- // if height is less than 1.25 of font height, turn off word wrap
- // commented out due to bug with band.break
- //if (rect.Height < FFontLineHeight * 1.25f)
- //FFormat.FormatFlags |= StringFormatFlags.NoWrap;
- // if word wrap is set, ignore trimming
- if (WordWrap)
- Format.Trimming = StringTrimming.Word;
- // LineLimit flag is essential in linux
- Format.FormatFlags = Format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.LineLimit;
- if (Angle != 0)
- {
- // shift displayrect
- displayRect.X = -DisplayRect.Width / 2;
- displayRect.Y = -DisplayRect.Height / 2;
- // rotate displayrect if angle is 90 or 270
- if ((Angle >= 90 && Angle < 180) || (Angle >= 270 && Angle < 360))
- displayRect = new RectangleF(DisplayRect.Y, DisplayRect.X, DisplayRect.Height, DisplayRect.Width);
- }
- displayRect.X /= WidthRatio;
- displayRect.Width /= WidthRatio;
- SplitToParagraphs(text);
- AdjustParagraphLines();
- // restore original values
- displayRect = rect;
- Format.FormatFlags = saveFlags;
- Format.Trimming = saveTrimming;
- }
- /// <summary>
- /// Paragraph represents single paragraph. It consists of one or several <see cref="Lines"/>.
- /// </summary>
- public class Paragraph
- {
- #region Fields
- private List<Line> lines;
- private AdvancedTextRenderer renderer;
- private string text;
- private int originalCharIndex;
- #endregion
- #region Properties
- public List<Line> Lines
- {
- get { return lines; }
- }
- public AdvancedTextRenderer Renderer
- {
- get { return renderer; }
- }
- public bool Last
- {
- get { return renderer.Paragraphs[renderer.Paragraphs.Count - 1] == this; }
- }
- public bool IsEmpty
- {
- get { return String.IsNullOrEmpty(text); }
- }
- public string Text
- {
- get { return text; }
- }
- #endregion
- #region Private Methods
- private int MeasureString(string text)
- {
- if (text.Length > 0)
- {
- // BEGIN: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b
- float left = 0;
- int tabFit = 0;
- while (text.Length > 0 && text[0] == '\t')
- {
- left = Renderer.GetTabPosition(left);
- text = text.Substring(1);
- if (Renderer.DisplayRect.Width < left)
- return tabFit;
- tabFit++;
- }
- if (tabFit > 0 && Renderer.DisplayRect.Width < left)
- return tabFit;
- int charsFit = 0;
- int linesFit = 0;
- // END: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b
- Renderer.Graphics.MeasureString(text, Renderer.Font,
- new SizeF(Renderer.DisplayRect.Width - left, Renderer.FontLineHeight * 1.25f),
- Renderer.Format, out charsFit, out linesFit);
- return charsFit + tabFit;
- }
- return 0;
- }
- #endregion
- #region Public Methods
- public void WrapLines()
- {
- string text = this.text;
- int charsFit = 0;
- if (String.IsNullOrEmpty(text))
- {
- lines.Add(new Line("", this, originalCharIndex));
- return;
- }
- if (Renderer.WordWrap)
- {
- int originalCharIndex = this.originalCharIndex;
- while (text.Length > 0)
- {
- charsFit = MeasureString(text);
- // avoid infinite loop when width of object less than width of one character
- if (charsFit == 0)
- {
- break;
- }
- string textFit = text.Substring(0, charsFit).TrimEnd(' ');
- lines.Add(new Line(textFit, this, originalCharIndex));
- text = text.Substring(charsFit)
- // Fix for linux system
- .TrimStart(' ');
- originalCharIndex += charsFit;
- }
- }
- else
- {
- string ellipsis = "\u2026";
- StringTrimming trimming = Renderer.Format.Trimming;
- if (trimming == StringTrimming.EllipsisPath)
- Renderer.Format.Trimming = StringTrimming.Character;
- charsFit = MeasureString(text);
- switch (trimming)
- {
- case StringTrimming.Character:
- case StringTrimming.Word:
- text = text.Substring(0, charsFit);
- break;
- case StringTrimming.EllipsisCharacter:
- case StringTrimming.EllipsisWord:
- if (charsFit < text.Length)
- {
- text = text.Substring(0, charsFit);
- if (text.EndsWith(" "))
- text = text.Substring(0, text.Length - 1);
- text += ellipsis;
- }
- break;
- case StringTrimming.EllipsisPath:
- if (charsFit < text.Length)
- {
- while (text.Length > 3)
- {
- int mid = text.Length / 2;
- string newText = text.Substring(0, mid) + ellipsis + text.Substring(mid + 1);
- if (MeasureString(newText) == newText.Length)
- {
- text = newText;
- break;
- }
- else
- {
- text = text.Remove(mid, 1);
- }
- }
- }
- break;
- }
- lines.Add(new Line(text, this, originalCharIndex));
- }
- }
- public StyleDescriptor WrapHtmlLines(StyleDescriptor style)
- {
- Line line = new Line("", this, this.originalCharIndex);
- lines.Add(line);
- Word word = new Word("", line);
- line.Words.Add(word);
- // for img
- //RunImage img = null;
- //end img
- string text = this.text;
- StringBuilder currentWord = new StringBuilder(100);
- float width = 0;
- bool skipSpace = true;
- int originalCharIndex = this.originalCharIndex;
- for (int i = 0; i < text.Length; i++)
- {
- char lastChar = text[i];
- if (lastChar == '&')
- {
- if (Converter.FromHtmlEntities(text, ref i, currentWord))
- {
- if (i >= text.Length - 1)
- {
- word.Runs.Add(new Run(currentWord.ToString(), style, word));
- // check width
- width += word.Width + Renderer.SpaceWidth;
- if (width > Renderer.DisplayRect.Width)
- {
- // line is too long, make a new line
- if (line.Words.Count > 1)
- {
- // if line has several words, delete the last word from the current line
- line.Words.RemoveAt(line.Words.Count - 1);
- // make new line
- line = new Line("", this, originalCharIndex);
- // and add word to it
- line.Words.Add(word);
- word.SetLine(line);
- lines.Add(line);
- }
- }
- #if DOTNET_4
- currentWord.Clear(); // .NET 2.0 doesn't have Clear()
- #else
- currentWord.Length = 0;
- #endif
- lastChar = ' ';
- }
- else
- {
- if (currentWord[currentWord.Length - 1] == '\t')
- {
- currentWord.Length--;
- lastChar = '\t';
- }
- else
- {
- continue;
- }
- }
- }
- }
- if (lastChar == '<')
- {
- // probably html tag
- StyleDescriptor newStyle = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine);
- newStyle.Font = style.Font;
- newStyle.Size = style.Size;
- string tag = "";
- bool match = false;
- // <b>, <i>, <u>
- if (i + 3 <= text.Length)
- {
- match = true;
- tag = text.Substring(i, 3).ToLower();
- if (tag == "<b>")
- newStyle.FontStyle |= FontStyle.Bold;
- else if (tag == "<i>")
- newStyle.FontStyle |= FontStyle.Italic;
- else if (tag == "<u>")
- newStyle.FontStyle |= FontStyle.Underline;
- else
- match = false;
- if (match)
- i += 3;
- }
- // </b>, </i>, </u>
- if (!match && i + 4 <= text.Length && text[i + 1] == '/')
- {
- match = true;
- tag = text.Substring(i, 4).ToLower();
- if (tag == "</b>")
- newStyle.FontStyle &= ~FontStyle.Bold;
- else if (tag == "</i>")
- newStyle.FontStyle &= ~FontStyle.Italic;
- else if (tag == "</u>")
- newStyle.FontStyle &= ~FontStyle.Underline;
- else
- match = false;
- if (match)
- i += 4;
- }
- // <sub>, <sup> // <img· // <font
- if (!match && i + 5 <= text.Length)
- {
- match = true;
- tag = text.Substring(i, 5).ToLower();
- if (tag == "<sub>")
- newStyle.BaseLine = BaseLine.Subscript;
- else if (tag == "<sup>")
- newStyle.BaseLine = BaseLine.Superscript;
- else if (tag == "<img ")
- {
- //try to found end tag
- int right = text.IndexOf('>', i + 5);
- if (right <= 0) match = false;
- else
- {
- //found img and parse them
- string src = null;
- string alt = " ";
- //currentWord = "";
- int src_ind = text.IndexOf("src=\"", i + 5);
- if (src_ind < right && src_ind >= 0)
- {
- src_ind += 5;
- int src_end = text.IndexOf("\"", src_ind);
- if (src_end < right && src_end >= 0)
- {
- src = text.Substring(src_ind, src_end - src_ind);
- }
- }
- int alt_ind = text.IndexOf("alt=\"", i + 5);
- if (alt_ind < right && alt_ind >= 0)
- {
- alt_ind += 5;
- int alt_end = text.IndexOf("\"", alt_ind);
- if (alt_end < right && alt_end >= 0)
- {
- alt = text.Substring(alt_ind, alt_end - alt_ind);
- }
- }
- //begin
- if (currentWord.Length != 0)
- {
- // finish the word
- word.Runs.Add(new Run(currentWord.ToString(), style, word));
- }
- #if DOTNET_4
- currentWord.Clear(); // .NET 2.0 doesn't have Clear()
- #else
- currentWord.Length = 0;
- #endif
- //end
- word.Runs.Add(new RunImage(src, alt, style, word));
- skipSpace = false;
- i = right - 4;
- }
- }
- else if (tag == "<font")
- {
- //try to found end of open tag
- int right = text.IndexOf('>', i + 5);
- if (right <= 0) match = false;
- else
- {
- //found font and parse them
- string color = null;
- string face = null;
- string size = null;
- int color_ind = text.IndexOf("color=\"", i + 5);
- if (color_ind < right && color_ind >= 0)
- {
- color_ind += 7;
- int color_end = text.IndexOf("\"", color_ind);
- if (color_end < right && color_end >= 0)
- {
- color = text.Substring(color_ind, color_end - color_ind);
- }
- }
- int face_ind = text.IndexOf("face=\"", i + 5);
- if (face_ind < right && face_ind >= 0)
- {
- face_ind += 6;
- int face_end = text.IndexOf("\"", face_ind);
- if (face_end < right && face_end >= 0)
- {
- face = text.Substring(face_ind, face_end - face_ind);
- }
- }
- int size_ind = text.IndexOf("size=\"", i + 5);
- if (size_ind < right && size_ind >= 0)
- {
- size_ind += 6;
- int size_end = text.IndexOf("\"", size_ind);
- if (size_end < right && size_end >= 0)
- {
- size = text.Substring(size_ind, size_end - size_ind);
- }
- }
- if (color != null)
- {
- if (color.StartsWith("\"") && color.EndsWith("\""))
- color = color.Substring(1, color.Length - 2);
- if (color.StartsWith("#"))
- {
- newStyle.Color = Color.FromArgb((int)(0xFF000000 + uint.Parse(color.Substring(1), NumberStyles.HexNumber)));
- }
- else
- {
- newStyle.Color = Color.FromName(color);
- }
- }
- newStyle.Font = face;
- if (size != null)
- {
- try
- {
- size = size.Trim(' ');
- switch (size[0])
- {
- case '-':
- size = size.Substring(1);
- if (style.Size == 0)
- newStyle.Size = Renderer.Font.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
- else
- newStyle.Size = style.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
- break;
- case '+':
- size = size.Substring(1);
- if (style.Size == 0)
- newStyle.Size = Renderer.Font.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
- else
- newStyle.Size = style.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
- break;
- default: newStyle.Size = (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; break;
- }
- if (newStyle.Size < 0) newStyle.Size = 0;
- }
- catch { }
- }
- i = right - 4;
- }
- }
- else
- match = false;
- if (match)
- i += 5;
- }
- // </sub>, </sup>
- if (!match && i + 6 <= text.Length && text[i + 1] == '/')
- {
- match = true;
- tag = text.Substring(i, 6).ToLower();
- if (tag == "</sub>")
- newStyle.BaseLine = BaseLine.Normal;
- else if (tag == "</sup>")
- newStyle.BaseLine = BaseLine.Normal;
- else
- match = false;
- if (match)
- i += 6;
- }
- // <strike>
- if (!match && i + 8 <= text.Length && text.Substring(i, 8).ToLower() == "<strike>")
- {
- newStyle.FontStyle |= FontStyle.Strikeout;
- match = true;
- i += 8;
- }
- // </strike>
- if (!match && i + 9 <= text.Length && text.Substring(i, 9).ToLower() == "</strike>")
- {
- newStyle.FontStyle &= ~FontStyle.Strikeout;
- match = true;
- i += 9;
- }
- /*
- // <font color
- if (!match && i + 12 < text.Length && text.Substring(i, 12).ToLower() == "<font color=")
- {
- int start = i + 12;
- int end = start;
- for (; end < text.Length && text[end] != '>'; end++)
- {
- }
- if (end < text.Length)
- {
- string colorName = text.Substring(start, end - start);
- if (colorName.StartsWith("\"") && colorName.EndsWith("\""))
- colorName = colorName.Substring(1, colorName.Length - 2);
- if (colorName.StartsWith("#"))
- {
- newStyle.Color = Color.FromArgb((int)(0xFF000000 + uint.Parse(colorName.Substring(1), NumberStyles.HexNumber)));
- }
- else
- {
- newStyle.Color = Color.FromName(colorName);
- }
- i = end + 1;
- match = true;
- }
- }
- */
- // </font>
- if (!match && i + 7 <= text.Length && text.Substring(i, 7).ToLower() == "</font>")
- {
- newStyle.Color = Renderer.BrushColor;
- newStyle.Size = 0;
- newStyle.Font = null;
- match = true;
- i += 7;
- }
- if (match)
- {
- if (currentWord.Length != 0)
- {
- // finish the word
- word.Runs.Add(new Run(currentWord.ToString(), style, word));
- }
- #if DOTNET_4
- currentWord.Clear(); // .NET 2.0 doesn't have Clear()
- #else
- currentWord.Length = 0;
- #endif
- style = newStyle;
- i--;
- if (i >= text.Length - 1)
- {
- // check width
- width += word.Width + Renderer.SpaceWidth;
- if (width > Renderer.DisplayRect.Width)
- {
- // line is too long, make a new line
- if (line.Words.Count > 1)
- {
- // if line has several words, delete the last word from the current line
- line.Words.RemoveAt(line.Words.Count - 1);
- // make new line
- line = new Line("", this, originalCharIndex);
- // and add word to it
- line.Words.Add(word);
- word.SetLine(line);
- lines.Add(line);
- }
- }
- }
- continue;
- }
- }
- if (lastChar == ' ' || lastChar == '\t' || i == text.Length - 1)
- {
- // finish the last word
- bool isLastWord = i == text.Length - 1;
- if (isLastWord)
- {
- currentWord.Append(lastChar);
- skipSpace = false;
- }
- if (lastChar == '\t')
- skipSpace = false;
- // space
- if (skipSpace)
- {
- currentWord.Append(lastChar);
- }
- else
- {
- // finish the word
- if (currentWord.Length != 0)
- word.Runs.Add(new Run(currentWord.ToString(), style, word));
- // check width
- width += word.Width + word.SpaceWidth;
- if (width > Renderer.DisplayRect.Width)
- {
- // line is too long, make a new line
- width = 0;
- if (line.Words.Count > 1)
- {
- // if line has several words, delete the last word from the current line
- line.Words.RemoveAt(line.Words.Count - 1);
- // make new line
- line = new Line("", this, originalCharIndex);
- // and add word to it
- line.Words.Add(word);
- word.SetLine(line);
- width += word.Width + word.SpaceWidth;
- }
- else
- {
- line = new Line("", this, i + 1);
- }
- lines.Add(line);
- }
- // TAB symbol
- if (lastChar == '\t')
- {
- if (currentWord.Length == 0 && line.Words.Count > 0 && line.Words[line.Words.Count - 1].Width == 0)
- line.Words.RemoveAt(line.Words.Count - 1);
- word = new Word("\t", line);
- line.Words.Add(word);
- // adjust width
- width = Renderer.GetTabPosition(width);
- }
- if (!isLastWord)
- {
- word = new Word("", line);
- line.Words.Add(word);
- #if DOTNET_4
- currentWord.Clear(); // .NET 2.0 doesn't have Clear()
- #else
- currentWord.Length = 0;
- #endif
- originalCharIndex = this.originalCharIndex + i + 1;
- skipSpace = true;
- }
- }
- }
- else
- {
- // symbol
- currentWord.Append(lastChar);
- skipSpace = false;
- }
- }
- return style;
- }
- public void AlignLines(bool forceJustify)
- {
- for (int i = 0; i < Lines.Count; i++)
- {
- HorzAlign align = Renderer.HorzAlign;
- if (align == HorzAlign.Justify && i == Lines.Count - 1 && !forceJustify)
- align = HorzAlign.Left;
- Lines[i].AlignWords(align);
- }
- }
- public void Draw()
- {
- foreach (Line line in Lines)
- {
- line.Draw();
- }
- }
- #endregion
- public Paragraph(string text, AdvancedTextRenderer renderer, int originalCharIndex)
- {
- lines = new List<Line>();
- this.text = text;
- this.renderer = renderer;
- this.originalCharIndex = originalCharIndex;
- }
- }
- /// <summary>
- /// Line represents single text line. It consists of one or several <see cref="Words"/>.
- /// Simple line (that does not contain tabs, html tags, and is not justified) has
- /// single <see cref="Word"/> which contains all the text.
- /// </summary>
- public class Line
- {
- #region Fields
- private List<Word> words;
- private string text;
- private bool hasTabs;
- private Paragraph paragraph;
- private float top;
- private float width;
- private int originalCharIndex;
- private List<RectangleF> underlines;
- private List<RectangleF> strikeouts;
- #endregion
- #region Properties
- public List<Word> Words
- {
- get { return words; }
- }
- public string Text
- {
- get { return text; }
- }
- public bool HasTabs
- {
- get { return hasTabs; }
- }
- public float Left
- {
- get { return Words.Count > 0 ? Words[0].Left : 0; }
- }
- public float Top
- {
- get { return top; }
- set { top = value; }
- }
- public float Width
- {
- get { return width; }
- }
- public int OriginalCharIndex
- {
- get { return originalCharIndex; }
- }
- public AdvancedTextRenderer Renderer
- {
- get { return paragraph.Renderer; }
- }
- public StyleDescriptor Style
- {
- get
- {
- if (Words.Count > 0)
- if (Words[0].Runs.Count > 0)
- return Words[0].Runs[0].Style;
- return null;
- }
- }
- public bool Last
- {
- get { return paragraph.Lines[paragraph.Lines.Count - 1] == this; }
- }
- public List<RectangleF> Underlines
- {
- get { return underlines; }
- }
- public List<RectangleF> Strikeouts
- {
- get { return strikeouts; }
- }
- #endregion
- #region Private Methods
- private void PrepareUnderlines(List<RectangleF> list, FontStyle style)
- {
- list.Clear();
- if (Words.Count == 0)
- return;
- if (Renderer.HtmlTags)
- {
- float left = 0;
- float right = 0;
- bool styleOn = false;
- foreach (Word word in Words)
- {
- foreach (Run run in word.Runs)
- {
- using (Font fnt = run.GetFont())
- {
- if ((fnt.Style & style) > 0)
- {
- if (!styleOn)
- {
- styleOn = true;
- left = run.Left;
- }
- right = run.Left + run.Width;
- }
- if ((fnt.Style & style) == 0 && styleOn)
- {
- styleOn = false;
- list.Add(new RectangleF(left, Top, right - left, 1));
- }
- }
- }
- }
- // close the style
- if (styleOn)
- list.Add(new RectangleF(left, Top, right - left, 1));
- }
- else if ((Renderer.Font.Style & style) > 0)
- {
- float lineWidth = Width;
- if (Renderer.HorzAlign == HorzAlign.Justify && (!Last || (paragraph.Last && Renderer.ForceJustify)))
- lineWidth = Renderer.DisplayRect.Width - Renderer.SpaceWidth;
- list.Add(new RectangleF(Left, Top, lineWidth, 1));
- }
- }
- #endregion
- #region Public Methods
- public void AlignWords(HorzAlign align)
- {
- width = 0;
- // handle each word
- if (align == HorzAlign.Justify || HasTabs || Renderer.Wysiwyg || Renderer.HtmlTags)
- {
- float left = 0;
- Word word = null;
- for (int i = 0; i < Words.Count; i++)
- {
- word = Words[i];
- word.Left = left;
- if (word.Text == "\t")
- {
- left = Renderer.GetTabPosition(left);
- // remove tab
- Words.RemoveAt(i);
- i--;
- }
- else
- left += word.Width + word.SpaceWidth;
- }
- if (word != null)
- width = left - word.SpaceWidth;
- else
- width = left - Renderer.SpaceWidth;
- }
- else
- {
- // join all words into one
- Words.Clear();
- Words.Add(new Word(text, this));
- width = Words[0].Width;
- }
- float rectWidth = Renderer.DisplayRect.Width;
- if (align == HorzAlign.Justify)
- {
- float delta = (rectWidth - width - Renderer.SpaceWidth) / (Words.Count - 1);
- float curDelta = delta;
- for (int i = 1; i < Words.Count; i++)
- {
- words[i].Left += curDelta;
- curDelta += delta;
- }
- }
- else
- {
- float delta = 0;
- if (align == HorzAlign.Center)
- delta = (rectWidth - width) / 2;
- else if (align == HorzAlign.Right)
- delta = rectWidth - width - Renderer.SpaceWidth;
- for (int i = 0; i < Words.Count; i++)
- {
- words[i].Left += delta;
- }
- }
- // adjust X offset
- foreach (Word word in Words)
- {
- if (Renderer.RightToLeft)
- word.Left = Renderer.DisplayRect.Right - word.Left;
- else
- word.Left += Renderer.DisplayRect.Left;
- word.AdjustRuns();
- if (Renderer.RightToLeft && Renderer.PDFMode)
- word.Left -= word.Width;
- }
- }
- public void MakeUnderlines()
- {
- PrepareUnderlines(underlines, FontStyle.Underline);
- PrepareUnderlines(strikeouts, FontStyle.Strikeout);
- }
- public void Draw()
- {
- foreach (Word word in Words)
- {
- word.Draw();
- }
- if (Underlines.Count > 0 || Strikeouts.Count > 0)
- {
- using (Pen pen = new Pen(Renderer.Brush, Renderer.Font.Size * 0.1f))
- {
- float h = Renderer.FontLineHeight;
- float w = h * 0.1f; // to match .net char X offset
- // invert offset in case of rtl
- if (Renderer.RightToLeft)
- w = -w;
- // emulate underline & strikeout
- foreach (RectangleF rect in Underlines)
- {
- Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h - w, rect.Right + w, rect.Top + h - w);
- }
- h /= 2;
- foreach (RectangleF rect in Strikeouts)
- {
- Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h, rect.Right + w, rect.Top + h);
- }
- }
- }
- }
- public float CalcHeight()
- {
- float height = -1;
- foreach (Word word in Words)
- {
- height = Math.Max(height, word.CalcHeight());
- }
- if (height < 0)
- height = Renderer.LineHeight;
- return height;
- }
- #endregion
- public Line(string text, Paragraph paragraph, int originalCharIndex)
- {
- this.words = new List<Word>();
- this.text = text;
- this.paragraph = paragraph;
- this.originalCharIndex = originalCharIndex;
- underlines = new List<RectangleF>();
- strikeouts = new List<RectangleF>();
- hasTabs = text.Contains("\t");
- // split text by spaces
- string[] words = text.Split(' ');
- string textWithSpaces = "";
- foreach (string word in words)
- {
- if (word == "")
- textWithSpaces += " ";
- else
- {
- // split text by tabs
- textWithSpaces += word;
- string[] tabWords = textWithSpaces.Split('\t');
- foreach (string word1 in tabWords)
- {
- if (word1 == "")
- this.words.Add(new Word("\t", this));
- else
- {
- this.words.Add(new Word(word1, this));
- this.words.Add(new Word("\t", this));
- }
- }
- // remove last tab
- this.words.RemoveAt(this.words.Count - 1);
- textWithSpaces = "";
- }
- }
- }
- internal float CalcBaseLine()
- {
- float baseline = 0;
- foreach (Word word in Words)
- {
- baseline = Math.Max(baseline, word.CalcBaseLine());
- }
- return baseline;
- }
- internal float CalcUnderBaseLine()
- {
- float underbaseline = 0;
- foreach (Word word in Words)
- {
- underbaseline = Math.Max(underbaseline, word.CalcUnderBaseLine());
- }
- return underbaseline;
- }
- }
- /// <summary>
- /// Word represents single word. It may consist of one or several <see cref="Runs"/>, in case
- /// when HtmlTags are enabled in the main <see cref="AdvancedTextRenderer"/> class.
- /// </summary>
- public class Word
- {
- #region Fields
- private List<Run> runs;
- protected string text;
- private float left;
- private float width;
- internal Line line;
- #endregion
- #region Properties
- public string Text
- {
- get { return text; }
- }
- public float Left
- {
- get { return left; }
- set { left = value; }
- }
- public float Width
- {
- get
- {
- if (width == -1)
- {
- if (Renderer.HtmlTags)
- {
- width = 0;
- foreach (Run run in Runs)
- {
- width += run.Width;
- }
- }
- else
- {
- width = Renderer.Graphics.MeasureString(text, Renderer.Font, 10000, StringFormat.GenericTypographic).Width;
- }
- }
- return width;
- }
- }
- public float Top
- {
- get { return line.Top; }
- }
- public AdvancedTextRenderer Renderer
- {
- get { return line.Renderer; }
- }
- public List<Run> Runs
- {
- get { return runs; }
- }
- public float SpaceWidth
- {
- get
- {
- if (Runs == null || Runs.Count == 0)
- return Renderer.SpaceWidth;
- return Runs[Runs.Count - 1].SpaceWidth;
- }
- }
- #endregion
- #region Public Methods
- public void AdjustRuns()
- {
- float left = Left;
- foreach (Run run in Runs)
- {
- run.Left = left;
- if (Renderer.RightToLeft)
- {
- left -= run.Width;
- if (Renderer.PDFMode)
- run.Left -= run.Width;
- }
- else
- left += run.Width;
- }
- }
- public void SetLine(Line line)
- {
- this.line = line;
- }
- public void Draw()
- {
- if (Renderer.HtmlTags)
- {
- foreach (Run run in Runs)
- {
- run.Draw();
- }
- }
- else
- {
- // don't draw underlines & strikeouts because they are drawn in the Line.Draw method
- Font font = Renderer.Font;
- bool disposeFont = false;
- if ((Renderer.Font.Style & FontStyle.Underline) > 0 || (Renderer.Font.Style & FontStyle.Strikeout) > 0)
- {
- font = new Font(Renderer.Font, Renderer.Font.Style & ~FontStyle.Underline & ~FontStyle.Strikeout);
- disposeFont = true;
- }
- if (Renderer.OutlinePen == null)
- {
- Renderer.Graphics.DrawString(Text, font, Renderer.Brush, Left, Top, Renderer.Format);
- }
- else
- {
- GraphicsPath path = new GraphicsPath();
- path.AddString(Text, font.FontFamily, Convert.ToInt32(font.Style), Renderer.Graphics.DpiY * font.Size / 72, new PointF(Left - 1, Top - 1), Renderer.Format);
- Renderer.Graphics.FillAndDrawPath(Renderer.OutlinePen, Renderer.Brush, path);
- }
- if (disposeFont)
- {
- font.Dispose();
- font = null;
- }
- }
- }
- internal float CalcHeight()
- {
- if (Renderer.HtmlTags)
- {
- float height = -1;
- foreach (Run run in Runs)
- {
- height = Math.Max(height, run.Height);
- }
- if (height < 0)
- height = Renderer.LineHeight;
- return height;
- }
- else
- {
- #if SKIA
- // we need actual height of a text because it may have font fallback with different metrics
- if (!string.IsNullOrEmpty(text))
- {
- return DrawUtils.MeasureString(Renderer.Graphics.Graphics, text, Renderer.Font, Renderer.Format).Height;
- }
- #endif
- return Renderer.LineHeight;
- }
- }
- internal float CalcBaseLine()
- {
- float baseLine = 0;
- if (Renderer.HtmlTags)
- {
- foreach (Run run in Runs)
- {
- baseLine = Math.Max(baseLine, run.CurrentBaseLine);
- }
- return baseLine;
- }
- else
- {
- return 0;
- }
- }
- internal float CalcUnderBaseLine()
- {
- float underbaseLine = 0;
- if (Renderer.HtmlTags)
- {
- foreach (Run run in Runs)
- {
- underbaseLine = Math.Max(underbaseLine, run.CurrentUnderBaseLine);
- }
- return underbaseLine;
- }
- else
- {
- return 0;
- }
- }
- #endregion
- public Word(string text, Line line)
- {
- this.text = text;
- runs = new List<Run>();
- this.line = line;
- width = -1;
- }
- }
- /// <summary>
- /// Represents character placement.
- /// </summary>
- public enum BaseLine
- {
- Normal,
- Subscript,
- Superscript
- }
- /// <summary>
- /// Represents a style used in HtmlTags mode.
- /// </summary>
- public class StyleDescriptor
- {
- #region Fields
- private FontStyle fontStyle;
- private Color color;
- private BaseLine baseLine;
- private string font;
- private float size;
- #endregion
- #region Properties
- public FontStyle FontStyle
- {
- get { return fontStyle; }
- set { fontStyle = value; }
- }
- public string Font
- {
- get { return font; }
- set { font = value; }
- }
- public float Size
- {
- get { return size; }
- set { size = value; }
- }
- public Color Color
- {
- get { return color; }
- set { color = value; }
- }
- public BaseLine BaseLine
- {
- get { return baseLine; }
- set { baseLine = value; }
- }
- #endregion
- #region Public Methods
- public override string ToString()
- {
- string result = "";
- if ((FontStyle & FontStyle.Bold) != 0)
- result += "<b>";
- if ((FontStyle & FontStyle.Italic) != 0)
- result += "<i>";
- if ((FontStyle & FontStyle.Underline) != 0)
- result += "<u>";
- if ((FontStyle & FontStyle.Strikeout) != 0)
- result += "<strike>";
- if (BaseLine == BaseLine.Subscript)
- result += "<sub>";
- if (BaseLine == BaseLine.Superscript)
- result += "<sup>";
- result += "<font color=\"";
- if (ColorExt.IsKnownColor(Color))
- result += Color.Name;
- else
- result += "#" + Color.ToArgb().ToString("x");
- result += "\"";
- if (Font != null)
- result += " face=\"" + Font + "\"";
- if (Size != 0)
- result += " size=\"" + Math.Round(Size).ToString() + "\"";
- result += ">";
- return result;
- }
- #endregion
- public StyleDescriptor(FontStyle fontStyle, Color color, BaseLine baseLine)
- {
- this.fontStyle = fontStyle;
- this.color = color;
- this.baseLine = baseLine;
- }
- }
- /// <summary>
- /// Represents sequence of characters that have the same <see cref="Style"/>.
- /// </summary>
- public class Run
- {
- #region Fields
- protected string text;
- private StyleDescriptor style;
- protected Word word;
- private float left;
- protected float width;
- protected float lineHeight;
- protected float fontLineHeight;
- private float baseLine;
- protected float underBaseLine;
- protected float spaceWidth;
- #endregion
- #region Properties
- public string Text
- {
- get { return text; }
- }
- public StyleDescriptor Style
- {
- get { return style; }
- }
- public AdvancedTextRenderer Renderer
- {
- get { return word.Renderer; }
- }
- public float Left
- {
- get { return left; }
- set { left = value; }
- }
- public float LineHeight
- {
- get
- {
- if (lineHeight == 0)
- {
- if (style.Font == null && style.Size <= 0)
- lineHeight = Renderer.LineHeight;
- else
- lineHeight = GetFont().GetHeight(Renderer.Graphics.Graphics);
- }
- return lineHeight;
- }
- }
- virtual public float CurrentBaseLine
- {
- get
- {
- if (baseLine < 0)
- {
- Font ff = GetFont();
- float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle);
- float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle);
- baseLine = FontLineHeight * ascent / lineSpace;
- underBaseLine = FontLineHeight - baseLine;
- }
- return baseLine;
- }
- }
- virtual public float CurrentUnderBaseLine
- {
- get
- {
- if (underBaseLine < 0)
- {
- Font ff = GetFont();
- float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle);
- float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle);
- baseLine = FontLineHeight * ascent / lineSpace;
- underBaseLine = FontLineHeight - baseLine;
- }
- return baseLine;
- }
- }
- public float FontLineHeight
- {
- get
- {
- if (fontLineHeight == 0)
- {
- if (style.Font == null && style.Size <= 0)
- fontLineHeight = Renderer.FontLineHeight;
- else
- fontLineHeight = GetFont().GetHeight(Renderer.Graphics.Graphics);
- }
- return fontLineHeight;
- }
- }
- public virtual float Top
- {
- get
- {
- float baseLine = 0;
- if (Style.BaseLine == BaseLine.Subscript)
- baseLine += FontLineHeight * 0.45f;
- else if (Style.BaseLine == BaseLine.Superscript)
- baseLine -= FontLineHeight * 0.15f;
- return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine;
- }
- }
- virtual public float Width
- {
- get { return width; }
- }
- virtual public float Height
- {
- get
- {
- return LineHeight;
- }
- }
- public float SpaceWidth
- {
- get
- {
- if (spaceWidth < 0)
- {
- spaceWidth = CalculateSpaceSize(Renderer.Graphics, GetFont());// Renderer.Graphics.MeasureString(" ", GetFont()).Width;
- }
- return spaceWidth;
- }
- }
- #endregion
- #region Private Methods
- private Font GetFont(bool disableUnderlinesStrikeouts)
- {
- float fontSize = Renderer.Font.Size;
- if (Style.Size != 0)
- fontSize = Style.Size;
- if (Style.BaseLine != BaseLine.Normal)
- fontSize *= 0.6f;
- FontStyle fontStyle = Style.FontStyle;
- if (disableUnderlinesStrikeouts)
- fontStyle = fontStyle & ~FontStyle.Underline & ~FontStyle.Strikeout;
- if (Style.Font != null)
- return new Font(Style.Font, fontSize, fontStyle);
- return new Font(Renderer.Font.FontFamily, fontSize, fontStyle);
- }
- #endregion
- #region Public Methods
- public Font GetFont()
- {
- return GetFont(false);
- }
- public Brush GetBrush()
- {
- return new SolidBrush(Style.Color);
- }
- public virtual void Draw()
- {
- using (Font font = GetFont(true))
- using (Brush brush = GetBrush())
- {
- Renderer.Graphics.DrawString(text, font, brush, Left, Top, Renderer.Format);
- }
- }
- #endregion
- public Run(string text, StyleDescriptor style, Word word)
- {
- baseLine = float.MinValue;
- underBaseLine = float.MinValue;
- this.text = text;
- this.style = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine);
- this.style.Font = style.Font;
- this.style.Size = style.Size;
- this.word = word;
- spaceWidth = -1;
- using (Font font = GetFont())
- {
- width = Renderer.Graphics.MeasureString(text, font, 10000, StringFormat.GenericTypographic).Width;
- }
- }
- }
- /// <summary>
- /// Represents inline Image.
- /// </summary>
- internal class RunImage : Run
- {
- public Image Image { get { return image; } }
- override public float Width
- {
- get
- {
- if (Image == null) return base.Width;
- return Image.Width;
- }
- }
- override public float Top
- {
- get
- {
- float baseLine = 0;
- if (Style.BaseLine == BaseLine.Subscript)
- baseLine += FontLineHeight * 0.45f;
- else if (Style.BaseLine == BaseLine.Superscript)
- baseLine -= FontLineHeight * 0.15f;
- return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine;
- }
- }
- override public float CurrentBaseLine
- {
- get
- {
- if (Image == null) return base.CurrentBaseLine;
- return Image.Height;
- }
- }
- override public float Height
- {
- get
- {
- if (Image == null) return base.Height;
- return Image.Height + word.line.CalcUnderBaseLine();
- }
- }
- private Image image;
- override public void Draw()
- {
- if (Image == null)
- {
- base.Draw();
- return;
- }
- Renderer.Graphics.DrawImage(Image, Left, Top);// (FText, font, brush, Left, Top, Renderer.Format);
- }
- public static Bitmap ResizeImage(Image image, float scale)
- {
- int width = (int)(image.Width * scale);
- int height = (int)(image.Height * scale);
- if (width == 0) width = 1;
- if (height == 0) height = 1;
- Rectangle destRect = new Rectangle(0, 0, width, height);
- Bitmap destImage = new Bitmap(width, height);
- destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
- using (Graphics graphics = System.Drawing.Graphics.FromImage(destImage))
- {
- graphics.CompositingMode = CompositingMode.SourceCopy;
- graphics.CompositingQuality = CompositingQuality.HighQuality;
- graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- graphics.SmoothingMode = SmoothingMode.HighQuality;
- graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
- using (System.Drawing.Imaging.ImageAttributes wrapMode = new System.Drawing.Imaging.ImageAttributes())
- {
- wrapMode.SetWrapMode(WrapMode.TileFlipXY);
- graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
- }
- }
- return destImage;
- }
- public RunImage(string src, string text, StyleDescriptor style, Word word) : base(text, style, word)
- {
- underBaseLine = 0;
- image = ResizeImage(InlineImageCache.Load(Renderer.Cache, src), Renderer.Scale);
- }
- }
- }
- /// <summary>
- /// Standard text renderer uses standard DrawString method to draw text. It also supports:
- /// - text rotation;
- /// - fonts with non-standard width ratio.
- /// In case your text is justified, or contains html tags, use the <see cref="AdvancedTextRenderer"/>
- /// class instead.
- /// </summary>
- internal class StandardTextRenderer
- {
- public static void Draw(string text, IGraphics g, Font font, Brush brush, Pen outlinePen,
- RectangleF rect, StringFormat format, int angle, float widthRatio)
- {
- IGraphicsState state = g.Save();
- g.SetClip(rect, CombineMode.Intersect);
- g.TranslateTransform(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2);
- g.RotateTransform(angle);
- rect.X = -rect.Width / 2;
- rect.Y = -rect.Height / 2;
- if ((angle >= 90 && angle < 180) || (angle >= 270 && angle < 360))
- rect = new RectangleF(rect.Y, rect.X, rect.Height, rect.Width);
- g.ScaleTransform(widthRatio, 1);
- rect.X /= widthRatio;
- rect.Width /= widthRatio;
- if (outlinePen == null)
- {
- g.DrawString(text, font, brush, rect, format);
- }
- else
- {
- GraphicsPath path = new GraphicsPath();
- path.AddString(text, font.FontFamily, Convert.ToInt32(font.Style), g.DpiY * font.Size / 72, rect, format);
- g.FillAndDrawPath(outlinePen, brush, path);
- }
- g.Restore(state);
- }
- }
- /// <summary>
- /// Cache for rendering img tags in textobject.
- /// You can use only HTTP[s] protocol with absolute urls.
- /// </summary>
- public class InlineImageCache : IDisposable
- {
- #region Private Fields
- private WebClient client;
- private Dictionary<string, CacheItem> items;
- private bool serialized;
- private object locker;
- #endregion Private Fields
- #region Public Properties
- /// <summary>
- /// Is serialized
- /// </summary>
- public bool Serialized { get { return serialized; } set { serialized = value; } }
- #endregion Public Properties
- #region Private Properties
- /// <summary>
- /// Get or set WebClient for downloading imgs by url
- /// </summary>
- private WebClient Client
- {
- get
- {
- if (client == null)
- {
- client = new WebClient();
- }
- return client;
- }
- set
- {
- client = value;
- }
- }
- #endregion Private Properties
- #region Public Events
- /// <summary>
- /// Occurs before image load
- /// </summary>
- public static event EventHandler<LoadEventArgs> AfterLoad;
- /// <summary>
- /// Occurs after image load
- /// </summary>
- public static event EventHandler<LoadEventArgs> BeforeLoad;
- #endregion Public Events
- #region Public Methods
- /// <summary>
- /// Enumerates all values
- /// </summary>
- /// <returns></returns>
- public IEnumerable<CacheItem> AllItems()
- {
- List<CacheItem> list = new List<CacheItem>();
- lock (locker)
- {
- if (items != null)
- {
- foreach (KeyValuePair<string, CacheItem> item in items)
- {
- item.Value.Src = item.Key;
- list.Add(item.Value);
- }
- }
- }
- return list;
- }
- /// <summary>
- /// Return CacheItem by src
- /// </summary>
- /// <param name="src">Src attribute from img tag</param>
- /// <returns></returns>
- public CacheItem Get(string src)
- {
- CacheItem item = null;
- if (!Validate(src))
- item = new CacheItem();
- if (String.IsNullOrEmpty(src))
- return item;
- lock (locker)
- {
- if (items == null)
- {
- items = new Dictionary<string, CacheItem>();
- if (item == null)
- item = new CacheItem();
- items[src] = item;
- Serialized = false;
- }
- if (items.ContainsKey(src))
- return items[src];
- }
- return item;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="src"></param>
- /// <returns></returns>
- public Image Load(string src)
- {
- CacheItem item = null;
- if (String.IsNullOrEmpty(src))
- item = new CacheItem();
- else
- lock (locker)
- {
- if (items == null)
- items = new Dictionary<string, CacheItem>();
- else
- if (items.ContainsKey(src))
- return items[src].Image;
- item = new CacheItem();
- if (Validate(src))
- {
- try
- {
- if (src.StartsWith("data:"))
- {
- item.Set(src.Substring(src.IndexOf("base64,") + "base64,".Length));
- }
- else
- item.Set(Client.DownloadData(src));
- }
- catch
- {
- item.Set("");
- }
- }
- items[src] = item;
- Serialized = false;
- }
- item.Src = src;
- return item.Image;
- }
- /// <summary>
- /// Set CacheItem by src
- /// </summary>
- /// <param name="src">Src attribute from img tag</param>
- /// <param name="item">CacheItem</param>
- /// <returns></returns>
- public CacheItem Set(string src, CacheItem item)
- {
- if (String.IsNullOrEmpty(src))
- return new CacheItem();
- lock (locker)
- {
- if (items == null)
- items = new Dictionary<string, CacheItem>();
- if (!Validate(src))
- item = new CacheItem();
- items[src] = item;
- Serialized = false;
- }
- item.Src = src;
- return item;
- }
- /// <summary>
- /// Validate src attribute from image
- /// </summary>
- /// <param name="src">Src attribute from img tag</param>
- /// <returns>return true if src is valid</returns>
- public bool Validate(string src)
- {
- if (String.IsNullOrEmpty(src))
- return false;
- src = src.ToLower();
- if (src.StartsWith("http://"))
- return true;
- if (src.StartsWith("https://"))
- return true;
- if (src.StartsWith("data:") && src.IndexOf("base64,") > 0)
- return true;
- return false;
- }
- #endregion Public Methods
- #region Internal Methods
- static internal Image Load(InlineImageCache cache, string src)
- {
- LoadEventArgs args = new LoadEventArgs(cache, src);
- if (BeforeLoad != null) BeforeLoad(null, args);
- Image result = null;
- if (!args.Handled) result = cache.Load(src);
- args.Handled = false;
- if (AfterLoad != null) AfterLoad(null, args);
- if (args.Handled)
- return cache.Get(src).Image;
- return result;
- }
- #endregion Internal Methods
- #region Public Constructors
- /// <inheritdoc/>
- public InlineImageCache()
- {
- locker = new object();
- client = null;
- }
- /// <summary>
- ///
- /// </summary>
- ~InlineImageCache()
- {
- Dispose(false);
- }
- #endregion Public Constructors
- #region Public Classes
- /// <summary>
- /// Item of image cache Dictionary
- /// </summary>
- public class CacheItem : IDisposable
- {
- #region Private Fields
- private string base64;
- private bool error;
- private Image image;
- //private int FId;
- private string src;
- private byte[] stream;
- #endregion Private Fields
- #region Public Properties
- /// <summary>
- /// Get Base64 string
- /// </summary>
- public string Base64
- {
- get
- {
- try
- {//For strange img tag
- if (base64 != null)
- return base64;
- if (stream != null)
- {
- base64 = Convert.ToBase64String(stream);
- return base64;
- }
- if (image != null)
- {
- using (MemoryStream ms = new MemoryStream())
- {
- image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
- ms.Flush();
- stream = ms.ToArray();
- }
- base64 = Convert.ToBase64String(stream);
- return base64;
- }
- }
- catch { }
- GetErrorImage();
- return "";
- }
- }
- /// <summary>
- /// Return true if has some error with Image
- /// </summary>
- public bool Error
- {
- get { return error; }
- }
- /// <summary>
- /// Get Image
- /// </summary>
- public Image Image
- {
- get
- {
- try
- {//for strange img tag
- if (image != null)
- return image;
- if (stream != null)
- {
- MemoryStream ms = new MemoryStream(stream);
- image = Bitmap.FromStream(ms);
- return image;
- }
- if (base64 != null)
- {
- this.stream = Convert.FromBase64String(base64);
- MemoryStream ms = new MemoryStream(stream);
- image = Bitmap.FromStream(ms);
- return image;
- }
- }
- catch { }
- return GetErrorImage();
- }
- }
- /// <summary>
- /// Get byte array
- /// </summary>
- public byte[] Stream
- {
- get
- {
- if (stream != null) return stream;
- if (base64 != null)
- {
- stream = Convert.FromBase64String(base64);
- return stream;
- }
- if (image != null)
- {
- using (MemoryStream ms = new MemoryStream())
- {
- image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
- ms.Flush();
- stream = ms.ToArray();
- }
- return stream;
- }
- return new byte[0];
- }
- }
- #endregion Public Properties
- #region Internal Properties
- internal string Src
- {
- get { return src; }
- set { src = value; }
- }
- #endregion Internal Properties
- #region Public Methods
- /// <summary>
- /// Return error image and set true to error property
- /// </summary>
- /// <returns></returns>
- public Image GetErrorImage()
- {
- error = true;
- base64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFdJREFUOE9jbGlq+c9AIqipq2GEawEZQAo4dvgYqoXD0QAGhv9ATyKCBY1PXBjANKEbBjSWOANA9mPRDBImzgCKXECVMMCTsojzwtAzAOQvUjCJmRe/cgDt6ZAkZx23LwAAAABJRU5ErkJggg==";
- src = "data:image/png;base64," + base64;
- stream = Convert.FromBase64String(base64);
- using (MemoryStream ms = new MemoryStream(stream))
- image = Bitmap.FromStream(ms);
- return image;
- }
- /// <summary>
- /// Set value for cache item
- /// </summary>
- /// <param name="base64">Image encoded base64 string</param>
- public void Set(string base64)
- {
- this.base64 = base64;
- image = null;
- stream = null;
- }
- /// <summary>
- /// Set value for cache item
- /// </summary>
- /// <param name="img">Image</param>
- public void Set(Image img)
- {
- base64 = null;
- image = img;
- stream = null;
- }
- /// <summary>
- /// Set value for cache item
- /// </summary>
- /// <param name="arr">Image</param>
- public void Set(byte[] arr)
- {
- base64 = null;
- image = null;
- stream = arr;
- }
- #region IDisposable Support
- private bool disposedValue = false; // To detect redundant calls
- /// <summary>
- ///
- /// </summary>
- /// <param name="disposing"></param>
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- if (image != null)
- {
- image.Dispose();
- image = null;
- }
- // TODO: dispose managed state (managed objects).
- }
- // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
- // TODO: set large fields to null.
- disposedValue = true;
- }
- }
- // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
- // ~CacheItem() {
- // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- // Dispose(false);
- // }
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- Dispose(true);
- // TODO: uncomment the following line if the finalizer is overridden above.
- // GC.SuppressFinalize(this);
- }
- #endregion
- #endregion Public Methods
- }
- /// <summary>
- /// WebClientEventArgs
- /// </summary>
- public class LoadEventArgs : EventArgs
- {
- #region Private Fields
- private InlineImageCache cache;
- private bool handled;
- private string source;
- #endregion Private Fields
- #region Public Properties
- /// <summary>
- /// Gets a cache
- /// </summary>
- public InlineImageCache Cache { get { return cache; } }
- /// <summary>
- /// Gets or sets a value indicating whether the event was handled.
- /// </summary>
- public bool Handled { get { return handled; } set { handled = value; } }
- /// <summary>
- /// Gets or sets a url from src attribue of img tag
- /// </summary>
- public string Source { get { return source; } set { source = value; } }
- #endregion Public Properties
- #region Internal Constructors
- internal LoadEventArgs(InlineImageCache c, string src)
- {
- cache = c;
- source = src;
- handled = false;
- }
- #endregion Internal Constructors
- }
- #region IDisposable Support
- private bool disposedValue = false; // To detect redundant calls
- /// <summary>
- ///
- /// </summary>
- /// <param name="disposing"></param>
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- // TODO: dispose managed state (managed objects).
- }
- if (this.items != null)
- {
- Dictionary<string, CacheItem> items = this.items;
- this.items = null;
- foreach (CacheItem item in items.Values)
- item.Dispose();
- }
- // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
- // TODO: set large fields to null.
- disposedValue = true;
- }
- }
- // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
- // ~InlineImageCache() {
- // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- // Dispose(false);
- // }
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- Dispose(true);
- // TODO: uncomment the following line if the finalizer is overridden above.
- // GC.SuppressFinalize(this);
- }
- #endregion
- #endregion Public Classes
- }
- }
|