TextRenderer.cs 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing.Drawing2D;
  4. using System.Drawing;
  5. using System.Globalization;
  6. using System.Text;
  7. using System.Net;
  8. using System.IO;
  9. namespace FastReport.Utils
  10. {
  11. /// <summary>
  12. /// Advanced text renderer is used to perform the following tasks:
  13. /// - draw justified text, text with custom line height, text containing html tags;
  14. /// - calculate text height, get part of text that does not fit in the display rectangle;
  15. /// - get paragraphs, lines, words and char sequence to perform accurate export to such
  16. /// formats as PDF, TXT, RTF
  17. /// </summary>
  18. /// <example>Here is how one may operate the renderer items:
  19. /// <code>
  20. /// foreach (AdvancedTextRenderer.Paragraph paragraph in renderer.Paragraphs)
  21. /// {
  22. /// foreach (AdvancedTextRenderer.Line line in paragraph.Lines)
  23. /// {
  24. /// foreach (AdvancedTextRenderer.Word word in line.Words)
  25. /// {
  26. /// if (renderer.HtmlTags)
  27. /// {
  28. /// foreach (AdvancedTextRenderer.Run run in word.Runs)
  29. /// {
  30. /// using (Font f = run.GetFont())
  31. /// using (Brush b = run.GetBrush())
  32. /// {
  33. /// g.DrawString(run.Text, f, b, run.Left, run.Top, renderer.Format);
  34. /// }
  35. /// }
  36. /// }
  37. /// else
  38. /// {
  39. /// g.DrawString(word.Text, renderer.Font, renderer.Brush, word.Left, word.Top, renderer.Format);
  40. /// }
  41. /// }
  42. /// }
  43. /// }
  44. /// </code>
  45. /// </example>
  46. public class AdvancedTextRenderer
  47. {
  48. #region Fields
  49. private List<Paragraph> paragraphs;
  50. private string text;
  51. private IGraphics graphics;
  52. private Font font;
  53. private Brush brush;
  54. private Pen outlinePen;
  55. private RectangleF displayRect;
  56. private StringFormat format;
  57. private HorzAlign horzAlign;
  58. private VertAlign vertAlign;
  59. private float lineHeight;
  60. private float fontLineHeight;
  61. private int angle;
  62. private float widthRatio;
  63. private bool forceJustify;
  64. private bool wysiwyg;
  65. private bool htmlTags;
  66. private bool pDFMode;
  67. private float spaceWidth;
  68. private float scale;
  69. private InlineImageCache cache;
  70. private float fontScale;
  71. #endregion
  72. #region Properties
  73. public List<Paragraph> Paragraphs
  74. {
  75. get { return paragraphs; }
  76. }
  77. public IGraphics Graphics
  78. {
  79. get { return graphics; }
  80. }
  81. public Font Font
  82. {
  83. get { return font; }
  84. }
  85. public Brush Brush
  86. {
  87. get { return brush; }
  88. }
  89. public Pen OutlinePen
  90. {
  91. get { return outlinePen; }
  92. }
  93. public Color BrushColor
  94. {
  95. get { return brush is SolidBrush ? (brush as SolidBrush).Color : Color.Black; }
  96. }
  97. public RectangleF DisplayRect
  98. {
  99. get { return displayRect; }
  100. }
  101. public StringFormat Format
  102. {
  103. get { return format; }
  104. }
  105. public HorzAlign HorzAlign
  106. {
  107. get { return horzAlign; }
  108. }
  109. public VertAlign VertAlign
  110. {
  111. get { return vertAlign; }
  112. }
  113. public float LineHeight
  114. {
  115. get { return lineHeight; }
  116. }
  117. public float FontLineHeight
  118. {
  119. get { return fontLineHeight; }
  120. }
  121. public int Angle
  122. {
  123. get { return angle; }
  124. }
  125. public float WidthRatio
  126. {
  127. get { return widthRatio; }
  128. }
  129. public bool ForceJustify
  130. {
  131. get { return forceJustify; }
  132. }
  133. public bool Wysiwyg
  134. {
  135. get { return wysiwyg; }
  136. }
  137. public bool HtmlTags
  138. {
  139. get { return htmlTags; }
  140. }
  141. public float TabSize
  142. {
  143. get
  144. {
  145. // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0]
  146. float firstTab = 0;
  147. float[] tabSizes = Format.GetTabStops(out firstTab);
  148. if (tabSizes.Length > 1)
  149. return tabSizes[1];
  150. return 0;
  151. }
  152. }
  153. public float TabOffset
  154. {
  155. get
  156. {
  157. // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0]
  158. float firstTab = 0;
  159. float[] tabSizes = Format.GetTabStops(out firstTab);
  160. if (tabSizes.Length > 0)
  161. return tabSizes[0];
  162. return 0;
  163. }
  164. }
  165. public bool WordWrap
  166. {
  167. get { return (Format.FormatFlags & StringFormatFlags.NoWrap) == 0; }
  168. }
  169. public bool RightToLeft
  170. {
  171. get { return (Format.FormatFlags & StringFormatFlags.DirectionRightToLeft) != 0; }
  172. }
  173. public bool PDFMode
  174. {
  175. get { return pDFMode; }
  176. }
  177. internal float SpaceWidth
  178. {
  179. get { return spaceWidth; }
  180. }
  181. /// <summary>
  182. /// The scale for font tag
  183. /// </summary>
  184. public float FontScale { get { return fontScale; } set { fontScale = value; } }
  185. public float Scale { get { return scale; } set { scale = value; } }
  186. public InlineImageCache Cache
  187. {
  188. get
  189. {
  190. if (cache == null)
  191. cache = new InlineImageCache();
  192. return cache;
  193. }
  194. }
  195. #endregion
  196. #region Private Methods
  197. const string ab = "abcdefabcdef";
  198. const string a40b = "abcdef abcdef";
  199. internal static float CalculateSpaceSize(IGraphics g, Font f)
  200. {
  201. float w_ab = g.MeasureString(ab, f).Width;
  202. float w_a40b = g.MeasureString(a40b, f).Width;
  203. return (w_a40b - w_ab) / 40;
  204. }
  205. private void SplitToParagraphs(string text)
  206. {
  207. StyleDescriptor style = new StyleDescriptor(Font.Style, BrushColor, BaseLine.Normal);
  208. if (HtmlTags)
  209. text = text.Replace("<br>", "\r\n").Replace("<br/>", "\r\n").Replace("<br />", "\r\n");
  210. string[] lines = text.Split('\n');
  211. int originalCharIndex = 0;
  212. foreach (string line in lines)
  213. {
  214. string s = line;
  215. if (s.Length > 0 && s[s.Length - 1] == '\r')
  216. s = s.Remove(s.Length - 1);
  217. Paragraph paragraph = new Paragraph(s, this, originalCharIndex);
  218. paragraphs.Add(paragraph);
  219. if (HtmlTags)
  220. style = paragraph.WrapHtmlLines(style);
  221. else
  222. paragraph.WrapLines();
  223. originalCharIndex += line.Length + 1;
  224. }
  225. // skip empty paragraphs at the end
  226. for (int i = paragraphs.Count - 1; i >= 0; i--)
  227. {
  228. if (paragraphs[i].IsEmpty && paragraphs.Count != 1)
  229. paragraphs.RemoveAt(i);
  230. else
  231. break;
  232. }
  233. }
  234. private void AdjustParagraphLines()
  235. {
  236. // calculate text height
  237. float height = CalcHeight();
  238. // calculate Y offset
  239. float offsetY = DisplayRect.Top;
  240. if (VertAlign == VertAlign.Center)
  241. offsetY += (DisplayRect.Height - height) / 2;
  242. else if (VertAlign == VertAlign.Bottom)
  243. offsetY += (DisplayRect.Height - height) - 1;
  244. for (int i = 0; i < Paragraphs.Count; i++)
  245. {
  246. Paragraph paragraph = Paragraphs[i];
  247. paragraph.AlignLines(i == Paragraphs.Count - 1 && ForceJustify);
  248. // adjust line tops
  249. foreach (Line line in paragraph.Lines)
  250. {
  251. line.Top = offsetY;
  252. line.MakeUnderlines();
  253. offsetY += line.CalcHeight();
  254. }
  255. }
  256. }
  257. #endregion
  258. #region Public Methods
  259. public void Draw()
  260. {
  261. // set clipping
  262. IGraphicsState state = Graphics.Save();
  263. Graphics.SetClip(DisplayRect, CombineMode.Intersect);
  264. // reset alignment
  265. StringAlignment saveAlign = Format.Alignment;
  266. StringAlignment saveLineAlign = Format.LineAlignment;
  267. Format.Alignment = StringAlignment.Near;
  268. Format.LineAlignment = StringAlignment.Near;
  269. if (Angle != 0)
  270. {
  271. Graphics.TranslateTransform(DisplayRect.Left + DisplayRect.Width / 2,
  272. DisplayRect.Top + DisplayRect.Height / 2);
  273. Graphics.RotateTransform(Angle);
  274. }
  275. Graphics.ScaleTransform(WidthRatio, 1);
  276. foreach (Paragraph paragraph in Paragraphs)
  277. {
  278. paragraph.Draw();
  279. }
  280. // restore alignment and clipping
  281. Format.Alignment = saveAlign;
  282. Format.LineAlignment = saveLineAlign;
  283. Graphics.Restore(state);
  284. }
  285. public float CalcHeight()
  286. {
  287. int charsFit = 0;
  288. StyleDescriptor style = null;
  289. return CalcHeight(out charsFit, out style);
  290. }
  291. public float CalcHeight(out int charsFit, out StyleDescriptor style)
  292. {
  293. charsFit = 0;
  294. style = null;
  295. float height = 0;
  296. float displayHeight = DisplayRect.Height;
  297. if (LineHeight > displayHeight)
  298. return 0;
  299. foreach (Paragraph paragraph in Paragraphs)
  300. {
  301. foreach (Line line in paragraph.Lines)
  302. {
  303. height += line.CalcHeight();
  304. if (charsFit == 0 && height > displayHeight)
  305. {
  306. charsFit = line.OriginalCharIndex;
  307. if (HtmlTags)
  308. style = line.Style;
  309. }
  310. }
  311. }
  312. if (charsFit == 0)
  313. charsFit = text.Length;
  314. return height;
  315. }
  316. public float CalcWidth()
  317. {
  318. float width = 0;
  319. foreach (Paragraph paragraph in Paragraphs)
  320. {
  321. foreach (Line line in paragraph.Lines)
  322. {
  323. if (width < line.Width)
  324. width = line.Width;
  325. }
  326. }
  327. return width + spaceWidth;
  328. }
  329. internal float GetTabPosition(float pos)
  330. {
  331. float tabOffset = TabOffset;
  332. float tabSize = TabSize;
  333. int tabPosition = (int)((pos - tabOffset) / tabSize);
  334. if (pos < tabOffset)
  335. return tabOffset;
  336. return (tabPosition + 1) * tabSize + tabOffset;
  337. }
  338. #endregion
  339. public AdvancedTextRenderer(string text, IGraphics g, Font font, Brush brush, Pen outlinePen,
  340. RectangleF rect, StringFormat format, HorzAlign horzAlign, VertAlign vertAlign,
  341. float lineHeight, int angle, float widthRatio,
  342. bool forceJustify, bool wysiwyg, bool htmlTags, bool pdfMode,
  343. float scale, float fontScale, InlineImageCache cache, bool isPrinting = false)
  344. {
  345. this.cache = cache;
  346. this.scale = scale;
  347. this.fontScale = fontScale;
  348. paragraphs = new List<Paragraph>();
  349. this.text = text;
  350. graphics = g;
  351. this.font = font;
  352. this.brush = brush;
  353. this.outlinePen = outlinePen;
  354. displayRect = rect;
  355. this.format = format;
  356. this.horzAlign = horzAlign;
  357. this.vertAlign = vertAlign;
  358. this.lineHeight = lineHeight;
  359. fontLineHeight = font.GetHeight(g.Graphics);
  360. if (this.lineHeight == 0)
  361. {
  362. this.lineHeight = fontLineHeight;
  363. if (isPrinting && Config.IsRunningOnMono && DrawUtils.GetMonoRendering(g.Graphics) == MonoRendering.Pango)
  364. {
  365. // we need this in order to fix inconsistent line spacing when print using Pango rendering
  366. this.lineHeight = fontLineHeight * 1.33f;
  367. }
  368. }
  369. this.angle = angle % 360;
  370. this.widthRatio = widthRatio;
  371. this.forceJustify = forceJustify;
  372. this.wysiwyg = wysiwyg;
  373. this.htmlTags = htmlTags;
  374. pDFMode = pdfMode;
  375. spaceWidth = CalculateSpaceSize(g, font);// g.MeasureString(" ", font).Width;
  376. StringFormatFlags saveFlags = Format.FormatFlags;
  377. StringTrimming saveTrimming = Format.Trimming;
  378. // match DrawString behavior:
  379. // if height is less than 1.25 of font height, turn off word wrap
  380. // commented out due to bug with band.break
  381. //if (rect.Height < FFontLineHeight * 1.25f)
  382. //FFormat.FormatFlags |= StringFormatFlags.NoWrap;
  383. // if word wrap is set, ignore trimming
  384. if (WordWrap)
  385. Format.Trimming = StringTrimming.Word;
  386. // LineLimit flag is essential in linux
  387. Format.FormatFlags = Format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.LineLimit;
  388. if (Angle != 0)
  389. {
  390. // shift displayrect
  391. displayRect.X = -DisplayRect.Width / 2;
  392. displayRect.Y = -DisplayRect.Height / 2;
  393. // rotate displayrect if angle is 90 or 270
  394. if ((Angle >= 90 && Angle < 180) || (Angle >= 270 && Angle < 360))
  395. displayRect = new RectangleF(DisplayRect.Y, DisplayRect.X, DisplayRect.Height, DisplayRect.Width);
  396. }
  397. displayRect.X /= WidthRatio;
  398. displayRect.Width /= WidthRatio;
  399. SplitToParagraphs(text);
  400. AdjustParagraphLines();
  401. // restore original values
  402. displayRect = rect;
  403. Format.FormatFlags = saveFlags;
  404. Format.Trimming = saveTrimming;
  405. }
  406. /// <summary>
  407. /// Paragraph represents single paragraph. It consists of one or several <see cref="Lines"/>.
  408. /// </summary>
  409. public class Paragraph
  410. {
  411. #region Fields
  412. private List<Line> lines;
  413. private AdvancedTextRenderer renderer;
  414. private string text;
  415. private int originalCharIndex;
  416. #endregion
  417. #region Properties
  418. public List<Line> Lines
  419. {
  420. get { return lines; }
  421. }
  422. public AdvancedTextRenderer Renderer
  423. {
  424. get { return renderer; }
  425. }
  426. public bool Last
  427. {
  428. get { return renderer.Paragraphs[renderer.Paragraphs.Count - 1] == this; }
  429. }
  430. public bool IsEmpty
  431. {
  432. get { return String.IsNullOrEmpty(text); }
  433. }
  434. public string Text
  435. {
  436. get { return text; }
  437. }
  438. #endregion
  439. #region Private Methods
  440. private int MeasureString(string text)
  441. {
  442. if (text.Length > 0)
  443. {
  444. // BEGIN: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b
  445. float left = 0;
  446. int tabFit = 0;
  447. while (text.Length > 0 && text[0] == '\t')
  448. {
  449. left = Renderer.GetTabPosition(left);
  450. text = text.Substring(1);
  451. if (Renderer.DisplayRect.Width < left)
  452. return tabFit;
  453. tabFit++;
  454. }
  455. if (tabFit > 0 && Renderer.DisplayRect.Width < left)
  456. return tabFit;
  457. int charsFit = 0;
  458. int linesFit = 0;
  459. // END: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b
  460. Renderer.Graphics.MeasureString(text, Renderer.Font,
  461. new SizeF(Renderer.DisplayRect.Width - left, Renderer.FontLineHeight * 1.25f),
  462. Renderer.Format, out charsFit, out linesFit);
  463. return charsFit + tabFit;
  464. }
  465. return 0;
  466. }
  467. #endregion
  468. #region Public Methods
  469. public void WrapLines()
  470. {
  471. string text = this.text;
  472. int charsFit = 0;
  473. if (String.IsNullOrEmpty(text))
  474. {
  475. lines.Add(new Line("", this, originalCharIndex));
  476. return;
  477. }
  478. if (Renderer.WordWrap)
  479. {
  480. int originalCharIndex = this.originalCharIndex;
  481. while (text.Length > 0)
  482. {
  483. charsFit = MeasureString(text);
  484. // avoid infinite loop when width of object less than width of one character
  485. if (charsFit == 0)
  486. {
  487. break;
  488. }
  489. string textFit = text.Substring(0, charsFit).TrimEnd(' ');
  490. lines.Add(new Line(textFit, this, originalCharIndex));
  491. text = text.Substring(charsFit)
  492. // Fix for linux system
  493. .TrimStart(' ');
  494. originalCharIndex += charsFit;
  495. }
  496. }
  497. else
  498. {
  499. string ellipsis = "\u2026";
  500. StringTrimming trimming = Renderer.Format.Trimming;
  501. if (trimming == StringTrimming.EllipsisPath)
  502. Renderer.Format.Trimming = StringTrimming.Character;
  503. charsFit = MeasureString(text);
  504. switch (trimming)
  505. {
  506. case StringTrimming.Character:
  507. case StringTrimming.Word:
  508. text = text.Substring(0, charsFit);
  509. break;
  510. case StringTrimming.EllipsisCharacter:
  511. case StringTrimming.EllipsisWord:
  512. if (charsFit < text.Length)
  513. {
  514. text = text.Substring(0, charsFit);
  515. if (text.EndsWith(" "))
  516. text = text.Substring(0, text.Length - 1);
  517. text += ellipsis;
  518. }
  519. break;
  520. case StringTrimming.EllipsisPath:
  521. if (charsFit < text.Length)
  522. {
  523. while (text.Length > 3)
  524. {
  525. int mid = text.Length / 2;
  526. string newText = text.Substring(0, mid) + ellipsis + text.Substring(mid + 1);
  527. if (MeasureString(newText) == newText.Length)
  528. {
  529. text = newText;
  530. break;
  531. }
  532. else
  533. {
  534. text = text.Remove(mid, 1);
  535. }
  536. }
  537. }
  538. break;
  539. }
  540. lines.Add(new Line(text, this, originalCharIndex));
  541. }
  542. }
  543. public StyleDescriptor WrapHtmlLines(StyleDescriptor style)
  544. {
  545. Line line = new Line("", this, this.originalCharIndex);
  546. lines.Add(line);
  547. Word word = new Word("", line);
  548. line.Words.Add(word);
  549. // for img
  550. //RunImage img = null;
  551. //end img
  552. string text = this.text;
  553. StringBuilder currentWord = new StringBuilder(100);
  554. float width = 0;
  555. bool skipSpace = true;
  556. int originalCharIndex = this.originalCharIndex;
  557. for (int i = 0; i < text.Length; i++)
  558. {
  559. char lastChar = text[i];
  560. if (lastChar == '&')
  561. {
  562. if (Converter.FromHtmlEntities(text, ref i, currentWord))
  563. {
  564. if (i >= text.Length - 1)
  565. {
  566. word.Runs.Add(new Run(currentWord.ToString(), style, word));
  567. // check width
  568. width += word.Width + Renderer.SpaceWidth;
  569. if (width > Renderer.DisplayRect.Width)
  570. {
  571. // line is too long, make a new line
  572. if (line.Words.Count > 1)
  573. {
  574. // if line has several words, delete the last word from the current line
  575. line.Words.RemoveAt(line.Words.Count - 1);
  576. // make new line
  577. line = new Line("", this, originalCharIndex);
  578. // and add word to it
  579. line.Words.Add(word);
  580. word.SetLine(line);
  581. lines.Add(line);
  582. }
  583. }
  584. #if DOTNET_4
  585. currentWord.Clear(); // .NET 2.0 doesn't have Clear()
  586. #else
  587. currentWord.Length = 0;
  588. #endif
  589. lastChar = ' ';
  590. }
  591. else
  592. {
  593. if (currentWord[currentWord.Length - 1] == '\t')
  594. {
  595. currentWord.Length--;
  596. lastChar = '\t';
  597. }
  598. else
  599. {
  600. continue;
  601. }
  602. }
  603. }
  604. }
  605. if (lastChar == '<')
  606. {
  607. // probably html tag
  608. StyleDescriptor newStyle = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine);
  609. newStyle.Font = style.Font;
  610. newStyle.Size = style.Size;
  611. string tag = "";
  612. bool match = false;
  613. // <b>, <i>, <u>
  614. if (i + 3 <= text.Length)
  615. {
  616. match = true;
  617. tag = text.Substring(i, 3).ToLower();
  618. if (tag == "<b>")
  619. newStyle.FontStyle |= FontStyle.Bold;
  620. else if (tag == "<i>")
  621. newStyle.FontStyle |= FontStyle.Italic;
  622. else if (tag == "<u>")
  623. newStyle.FontStyle |= FontStyle.Underline;
  624. else
  625. match = false;
  626. if (match)
  627. i += 3;
  628. }
  629. // </b>, </i>, </u>
  630. if (!match && i + 4 <= text.Length && text[i + 1] == '/')
  631. {
  632. match = true;
  633. tag = text.Substring(i, 4).ToLower();
  634. if (tag == "</b>")
  635. newStyle.FontStyle &= ~FontStyle.Bold;
  636. else if (tag == "</i>")
  637. newStyle.FontStyle &= ~FontStyle.Italic;
  638. else if (tag == "</u>")
  639. newStyle.FontStyle &= ~FontStyle.Underline;
  640. else
  641. match = false;
  642. if (match)
  643. i += 4;
  644. }
  645. // <sub>, <sup> // <img· // <font
  646. if (!match && i + 5 <= text.Length)
  647. {
  648. match = true;
  649. tag = text.Substring(i, 5).ToLower();
  650. if (tag == "<sub>")
  651. newStyle.BaseLine = BaseLine.Subscript;
  652. else if (tag == "<sup>")
  653. newStyle.BaseLine = BaseLine.Superscript;
  654. else if (tag == "<img ")
  655. {
  656. //try to found end tag
  657. int right = text.IndexOf('>', i + 5);
  658. if (right <= 0) match = false;
  659. else
  660. {
  661. //found img and parse them
  662. string src = null;
  663. string alt = " ";
  664. //currentWord = "";
  665. int src_ind = text.IndexOf("src=\"", i + 5);
  666. if (src_ind < right && src_ind >= 0)
  667. {
  668. src_ind += 5;
  669. int src_end = text.IndexOf("\"", src_ind);
  670. if (src_end < right && src_end >= 0)
  671. {
  672. src = text.Substring(src_ind, src_end - src_ind);
  673. }
  674. }
  675. int alt_ind = text.IndexOf("alt=\"", i + 5);
  676. if (alt_ind < right && alt_ind >= 0)
  677. {
  678. alt_ind += 5;
  679. int alt_end = text.IndexOf("\"", alt_ind);
  680. if (alt_end < right && alt_end >= 0)
  681. {
  682. alt = text.Substring(alt_ind, alt_end - alt_ind);
  683. }
  684. }
  685. //begin
  686. if (currentWord.Length != 0)
  687. {
  688. // finish the word
  689. word.Runs.Add(new Run(currentWord.ToString(), style, word));
  690. }
  691. #if DOTNET_4
  692. currentWord.Clear(); // .NET 2.0 doesn't have Clear()
  693. #else
  694. currentWord.Length = 0;
  695. #endif
  696. //end
  697. word.Runs.Add(new RunImage(src, alt, style, word));
  698. skipSpace = false;
  699. i = right - 4;
  700. }
  701. }
  702. else if (tag == "<font")
  703. {
  704. //try to found end of open tag
  705. int right = text.IndexOf('>', i + 5);
  706. if (right <= 0) match = false;
  707. else
  708. {
  709. //found font and parse them
  710. string color = null;
  711. string face = null;
  712. string size = null;
  713. int color_ind = text.IndexOf("color=\"", i + 5);
  714. if (color_ind < right && color_ind >= 0)
  715. {
  716. color_ind += 7;
  717. int color_end = text.IndexOf("\"", color_ind);
  718. if (color_end < right && color_end >= 0)
  719. {
  720. color = text.Substring(color_ind, color_end - color_ind);
  721. }
  722. }
  723. int face_ind = text.IndexOf("face=\"", i + 5);
  724. if (face_ind < right && face_ind >= 0)
  725. {
  726. face_ind += 6;
  727. int face_end = text.IndexOf("\"", face_ind);
  728. if (face_end < right && face_end >= 0)
  729. {
  730. face = text.Substring(face_ind, face_end - face_ind);
  731. }
  732. }
  733. int size_ind = text.IndexOf("size=\"", i + 5);
  734. if (size_ind < right && size_ind >= 0)
  735. {
  736. size_ind += 6;
  737. int size_end = text.IndexOf("\"", size_ind);
  738. if (size_end < right && size_end >= 0)
  739. {
  740. size = text.Substring(size_ind, size_end - size_ind);
  741. }
  742. }
  743. if (color != null)
  744. {
  745. if (color.StartsWith("\"") && color.EndsWith("\""))
  746. color = color.Substring(1, color.Length - 2);
  747. if (color.StartsWith("#"))
  748. {
  749. newStyle.Color = Color.FromArgb((int)(0xFF000000 + uint.Parse(color.Substring(1), NumberStyles.HexNumber)));
  750. }
  751. else
  752. {
  753. newStyle.Color = Color.FromName(color);
  754. }
  755. }
  756. newStyle.Font = face;
  757. if (size != null)
  758. {
  759. try
  760. {
  761. size = size.Trim(' ');
  762. switch (size[0])
  763. {
  764. case '-':
  765. size = size.Substring(1);
  766. if (style.Size == 0)
  767. newStyle.Size = Renderer.Font.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
  768. else
  769. newStyle.Size = style.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
  770. break;
  771. case '+':
  772. size = size.Substring(1);
  773. if (style.Size == 0)
  774. newStyle.Size = Renderer.Font.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
  775. else
  776. newStyle.Size = style.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale;
  777. break;
  778. default: newStyle.Size = (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; break;
  779. }
  780. if (newStyle.Size < 0) newStyle.Size = 0;
  781. }
  782. catch { }
  783. }
  784. i = right - 4;
  785. }
  786. }
  787. else
  788. match = false;
  789. if (match)
  790. i += 5;
  791. }
  792. // </sub>, </sup>
  793. if (!match && i + 6 <= text.Length && text[i + 1] == '/')
  794. {
  795. match = true;
  796. tag = text.Substring(i, 6).ToLower();
  797. if (tag == "</sub>")
  798. newStyle.BaseLine = BaseLine.Normal;
  799. else if (tag == "</sup>")
  800. newStyle.BaseLine = BaseLine.Normal;
  801. else
  802. match = false;
  803. if (match)
  804. i += 6;
  805. }
  806. // <strike>
  807. if (!match && i + 8 <= text.Length && text.Substring(i, 8).ToLower() == "<strike>")
  808. {
  809. newStyle.FontStyle |= FontStyle.Strikeout;
  810. match = true;
  811. i += 8;
  812. }
  813. // </strike>
  814. if (!match && i + 9 <= text.Length && text.Substring(i, 9).ToLower() == "</strike>")
  815. {
  816. newStyle.FontStyle &= ~FontStyle.Strikeout;
  817. match = true;
  818. i += 9;
  819. }
  820. /*
  821. // <font color
  822. if (!match && i + 12 < text.Length && text.Substring(i, 12).ToLower() == "<font color=")
  823. {
  824. int start = i + 12;
  825. int end = start;
  826. for (; end < text.Length && text[end] != '>'; end++)
  827. {
  828. }
  829. if (end < text.Length)
  830. {
  831. string colorName = text.Substring(start, end - start);
  832. if (colorName.StartsWith("\"") && colorName.EndsWith("\""))
  833. colorName = colorName.Substring(1, colorName.Length - 2);
  834. if (colorName.StartsWith("#"))
  835. {
  836. newStyle.Color = Color.FromArgb((int)(0xFF000000 + uint.Parse(colorName.Substring(1), NumberStyles.HexNumber)));
  837. }
  838. else
  839. {
  840. newStyle.Color = Color.FromName(colorName);
  841. }
  842. i = end + 1;
  843. match = true;
  844. }
  845. }
  846. */
  847. // </font>
  848. if (!match && i + 7 <= text.Length && text.Substring(i, 7).ToLower() == "</font>")
  849. {
  850. newStyle.Color = Renderer.BrushColor;
  851. newStyle.Size = 0;
  852. newStyle.Font = null;
  853. match = true;
  854. i += 7;
  855. }
  856. if (match)
  857. {
  858. if (currentWord.Length != 0)
  859. {
  860. // finish the word
  861. word.Runs.Add(new Run(currentWord.ToString(), style, word));
  862. }
  863. #if DOTNET_4
  864. currentWord.Clear(); // .NET 2.0 doesn't have Clear()
  865. #else
  866. currentWord.Length = 0;
  867. #endif
  868. style = newStyle;
  869. i--;
  870. if (i >= text.Length - 1)
  871. {
  872. // check width
  873. width += word.Width + Renderer.SpaceWidth;
  874. if (width > Renderer.DisplayRect.Width)
  875. {
  876. // line is too long, make a new line
  877. if (line.Words.Count > 1)
  878. {
  879. // if line has several words, delete the last word from the current line
  880. line.Words.RemoveAt(line.Words.Count - 1);
  881. // make new line
  882. line = new Line("", this, originalCharIndex);
  883. // and add word to it
  884. line.Words.Add(word);
  885. word.SetLine(line);
  886. lines.Add(line);
  887. }
  888. }
  889. }
  890. continue;
  891. }
  892. }
  893. if (lastChar == ' ' || lastChar == '\t' || i == text.Length - 1)
  894. {
  895. // finish the last word
  896. bool isLastWord = i == text.Length - 1;
  897. if (isLastWord)
  898. {
  899. currentWord.Append(lastChar);
  900. skipSpace = false;
  901. }
  902. if (lastChar == '\t')
  903. skipSpace = false;
  904. // space
  905. if (skipSpace)
  906. {
  907. currentWord.Append(lastChar);
  908. }
  909. else
  910. {
  911. // finish the word
  912. if (currentWord.Length != 0)
  913. word.Runs.Add(new Run(currentWord.ToString(), style, word));
  914. // check width
  915. width += word.Width + word.SpaceWidth;
  916. if (width > Renderer.DisplayRect.Width)
  917. {
  918. // line is too long, make a new line
  919. width = 0;
  920. if (line.Words.Count > 1)
  921. {
  922. // if line has several words, delete the last word from the current line
  923. line.Words.RemoveAt(line.Words.Count - 1);
  924. // make new line
  925. line = new Line("", this, originalCharIndex);
  926. // and add word to it
  927. line.Words.Add(word);
  928. word.SetLine(line);
  929. width += word.Width + word.SpaceWidth;
  930. }
  931. else
  932. {
  933. line = new Line("", this, i + 1);
  934. }
  935. lines.Add(line);
  936. }
  937. // TAB symbol
  938. if (lastChar == '\t')
  939. {
  940. if (currentWord.Length == 0 && line.Words.Count > 0 && line.Words[line.Words.Count - 1].Width == 0)
  941. line.Words.RemoveAt(line.Words.Count - 1);
  942. word = new Word("\t", line);
  943. line.Words.Add(word);
  944. // adjust width
  945. width = Renderer.GetTabPosition(width);
  946. }
  947. if (!isLastWord)
  948. {
  949. word = new Word("", line);
  950. line.Words.Add(word);
  951. #if DOTNET_4
  952. currentWord.Clear(); // .NET 2.0 doesn't have Clear()
  953. #else
  954. currentWord.Length = 0;
  955. #endif
  956. originalCharIndex = this.originalCharIndex + i + 1;
  957. skipSpace = true;
  958. }
  959. }
  960. }
  961. else
  962. {
  963. // symbol
  964. currentWord.Append(lastChar);
  965. skipSpace = false;
  966. }
  967. }
  968. return style;
  969. }
  970. public void AlignLines(bool forceJustify)
  971. {
  972. for (int i = 0; i < Lines.Count; i++)
  973. {
  974. HorzAlign align = Renderer.HorzAlign;
  975. if (align == HorzAlign.Justify && i == Lines.Count - 1 && !forceJustify)
  976. align = HorzAlign.Left;
  977. Lines[i].AlignWords(align);
  978. }
  979. }
  980. public void Draw()
  981. {
  982. foreach (Line line in Lines)
  983. {
  984. line.Draw();
  985. }
  986. }
  987. #endregion
  988. public Paragraph(string text, AdvancedTextRenderer renderer, int originalCharIndex)
  989. {
  990. lines = new List<Line>();
  991. this.text = text;
  992. this.renderer = renderer;
  993. this.originalCharIndex = originalCharIndex;
  994. }
  995. }
  996. /// <summary>
  997. /// Line represents single text line. It consists of one or several <see cref="Words"/>.
  998. /// Simple line (that does not contain tabs, html tags, and is not justified) has
  999. /// single <see cref="Word"/> which contains all the text.
  1000. /// </summary>
  1001. public class Line
  1002. {
  1003. #region Fields
  1004. private List<Word> words;
  1005. private string text;
  1006. private bool hasTabs;
  1007. private Paragraph paragraph;
  1008. private float top;
  1009. private float width;
  1010. private int originalCharIndex;
  1011. private List<RectangleF> underlines;
  1012. private List<RectangleF> strikeouts;
  1013. #endregion
  1014. #region Properties
  1015. public List<Word> Words
  1016. {
  1017. get { return words; }
  1018. }
  1019. public string Text
  1020. {
  1021. get { return text; }
  1022. }
  1023. public bool HasTabs
  1024. {
  1025. get { return hasTabs; }
  1026. }
  1027. public float Left
  1028. {
  1029. get { return Words.Count > 0 ? Words[0].Left : 0; }
  1030. }
  1031. public float Top
  1032. {
  1033. get { return top; }
  1034. set { top = value; }
  1035. }
  1036. public float Width
  1037. {
  1038. get { return width; }
  1039. }
  1040. public int OriginalCharIndex
  1041. {
  1042. get { return originalCharIndex; }
  1043. }
  1044. public AdvancedTextRenderer Renderer
  1045. {
  1046. get { return paragraph.Renderer; }
  1047. }
  1048. public StyleDescriptor Style
  1049. {
  1050. get
  1051. {
  1052. if (Words.Count > 0)
  1053. if (Words[0].Runs.Count > 0)
  1054. return Words[0].Runs[0].Style;
  1055. return null;
  1056. }
  1057. }
  1058. public bool Last
  1059. {
  1060. get { return paragraph.Lines[paragraph.Lines.Count - 1] == this; }
  1061. }
  1062. public List<RectangleF> Underlines
  1063. {
  1064. get { return underlines; }
  1065. }
  1066. public List<RectangleF> Strikeouts
  1067. {
  1068. get { return strikeouts; }
  1069. }
  1070. #endregion
  1071. #region Private Methods
  1072. private void PrepareUnderlines(List<RectangleF> list, FontStyle style)
  1073. {
  1074. list.Clear();
  1075. if (Words.Count == 0)
  1076. return;
  1077. if (Renderer.HtmlTags)
  1078. {
  1079. float left = 0;
  1080. float right = 0;
  1081. bool styleOn = false;
  1082. foreach (Word word in Words)
  1083. {
  1084. foreach (Run run in word.Runs)
  1085. {
  1086. using (Font fnt = run.GetFont())
  1087. {
  1088. if ((fnt.Style & style) > 0)
  1089. {
  1090. if (!styleOn)
  1091. {
  1092. styleOn = true;
  1093. left = run.Left;
  1094. }
  1095. right = run.Left + run.Width;
  1096. }
  1097. if ((fnt.Style & style) == 0 && styleOn)
  1098. {
  1099. styleOn = false;
  1100. list.Add(new RectangleF(left, Top, right - left, 1));
  1101. }
  1102. }
  1103. }
  1104. }
  1105. // close the style
  1106. if (styleOn)
  1107. list.Add(new RectangleF(left, Top, right - left, 1));
  1108. }
  1109. else if ((Renderer.Font.Style & style) > 0)
  1110. {
  1111. float lineWidth = Width;
  1112. if (Renderer.HorzAlign == HorzAlign.Justify && (!Last || (paragraph.Last && Renderer.ForceJustify)))
  1113. lineWidth = Renderer.DisplayRect.Width - Renderer.SpaceWidth;
  1114. list.Add(new RectangleF(Left, Top, lineWidth, 1));
  1115. }
  1116. }
  1117. #endregion
  1118. #region Public Methods
  1119. public void AlignWords(HorzAlign align)
  1120. {
  1121. width = 0;
  1122. // handle each word
  1123. if (align == HorzAlign.Justify || HasTabs || Renderer.Wysiwyg || Renderer.HtmlTags)
  1124. {
  1125. float left = 0;
  1126. Word word = null;
  1127. for (int i = 0; i < Words.Count; i++)
  1128. {
  1129. word = Words[i];
  1130. word.Left = left;
  1131. if (word.Text == "\t")
  1132. {
  1133. left = Renderer.GetTabPosition(left);
  1134. // remove tab
  1135. Words.RemoveAt(i);
  1136. i--;
  1137. }
  1138. else
  1139. left += word.Width + word.SpaceWidth;
  1140. }
  1141. if (word != null)
  1142. width = left - word.SpaceWidth;
  1143. else
  1144. width = left - Renderer.SpaceWidth;
  1145. }
  1146. else
  1147. {
  1148. // join all words into one
  1149. Words.Clear();
  1150. Words.Add(new Word(text, this));
  1151. width = Words[0].Width;
  1152. }
  1153. float rectWidth = Renderer.DisplayRect.Width;
  1154. if (align == HorzAlign.Justify)
  1155. {
  1156. float delta = (rectWidth - width - Renderer.SpaceWidth) / (Words.Count - 1);
  1157. float curDelta = delta;
  1158. for (int i = 1; i < Words.Count; i++)
  1159. {
  1160. words[i].Left += curDelta;
  1161. curDelta += delta;
  1162. }
  1163. }
  1164. else
  1165. {
  1166. float delta = 0;
  1167. if (align == HorzAlign.Center)
  1168. delta = (rectWidth - width) / 2;
  1169. else if (align == HorzAlign.Right)
  1170. delta = rectWidth - width - Renderer.SpaceWidth;
  1171. for (int i = 0; i < Words.Count; i++)
  1172. {
  1173. words[i].Left += delta;
  1174. }
  1175. }
  1176. // adjust X offset
  1177. foreach (Word word in Words)
  1178. {
  1179. if (Renderer.RightToLeft)
  1180. word.Left = Renderer.DisplayRect.Right - word.Left;
  1181. else
  1182. word.Left += Renderer.DisplayRect.Left;
  1183. word.AdjustRuns();
  1184. if (Renderer.RightToLeft && Renderer.PDFMode)
  1185. word.Left -= word.Width;
  1186. }
  1187. }
  1188. public void MakeUnderlines()
  1189. {
  1190. PrepareUnderlines(underlines, FontStyle.Underline);
  1191. PrepareUnderlines(strikeouts, FontStyle.Strikeout);
  1192. }
  1193. public void Draw()
  1194. {
  1195. foreach (Word word in Words)
  1196. {
  1197. word.Draw();
  1198. }
  1199. if (Underlines.Count > 0 || Strikeouts.Count > 0)
  1200. {
  1201. using (Pen pen = new Pen(Renderer.Brush, Renderer.Font.Size * 0.1f))
  1202. {
  1203. float h = Renderer.FontLineHeight;
  1204. float w = h * 0.1f; // to match .net char X offset
  1205. // invert offset in case of rtl
  1206. if (Renderer.RightToLeft)
  1207. w = -w;
  1208. // emulate underline & strikeout
  1209. foreach (RectangleF rect in Underlines)
  1210. {
  1211. Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h - w, rect.Right + w, rect.Top + h - w);
  1212. }
  1213. h /= 2;
  1214. foreach (RectangleF rect in Strikeouts)
  1215. {
  1216. Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h, rect.Right + w, rect.Top + h);
  1217. }
  1218. }
  1219. }
  1220. }
  1221. public float CalcHeight()
  1222. {
  1223. float height = -1;
  1224. foreach (Word word in Words)
  1225. {
  1226. height = Math.Max(height, word.CalcHeight());
  1227. }
  1228. if (height < 0)
  1229. height = Renderer.LineHeight;
  1230. return height;
  1231. }
  1232. #endregion
  1233. public Line(string text, Paragraph paragraph, int originalCharIndex)
  1234. {
  1235. this.words = new List<Word>();
  1236. this.text = text;
  1237. this.paragraph = paragraph;
  1238. this.originalCharIndex = originalCharIndex;
  1239. underlines = new List<RectangleF>();
  1240. strikeouts = new List<RectangleF>();
  1241. hasTabs = text.Contains("\t");
  1242. // split text by spaces
  1243. string[] words = text.Split(' ');
  1244. string textWithSpaces = "";
  1245. foreach (string word in words)
  1246. {
  1247. if (word == "")
  1248. textWithSpaces += " ";
  1249. else
  1250. {
  1251. // split text by tabs
  1252. textWithSpaces += word;
  1253. string[] tabWords = textWithSpaces.Split('\t');
  1254. foreach (string word1 in tabWords)
  1255. {
  1256. if (word1 == "")
  1257. this.words.Add(new Word("\t", this));
  1258. else
  1259. {
  1260. this.words.Add(new Word(word1, this));
  1261. this.words.Add(new Word("\t", this));
  1262. }
  1263. }
  1264. // remove last tab
  1265. this.words.RemoveAt(this.words.Count - 1);
  1266. textWithSpaces = "";
  1267. }
  1268. }
  1269. }
  1270. internal float CalcBaseLine()
  1271. {
  1272. float baseline = 0;
  1273. foreach (Word word in Words)
  1274. {
  1275. baseline = Math.Max(baseline, word.CalcBaseLine());
  1276. }
  1277. return baseline;
  1278. }
  1279. internal float CalcUnderBaseLine()
  1280. {
  1281. float underbaseline = 0;
  1282. foreach (Word word in Words)
  1283. {
  1284. underbaseline = Math.Max(underbaseline, word.CalcUnderBaseLine());
  1285. }
  1286. return underbaseline;
  1287. }
  1288. }
  1289. /// <summary>
  1290. /// Word represents single word. It may consist of one or several <see cref="Runs"/>, in case
  1291. /// when HtmlTags are enabled in the main <see cref="AdvancedTextRenderer"/> class.
  1292. /// </summary>
  1293. public class Word
  1294. {
  1295. #region Fields
  1296. private List<Run> runs;
  1297. protected string text;
  1298. private float left;
  1299. private float width;
  1300. internal Line line;
  1301. #endregion
  1302. #region Properties
  1303. public string Text
  1304. {
  1305. get { return text; }
  1306. }
  1307. public float Left
  1308. {
  1309. get { return left; }
  1310. set { left = value; }
  1311. }
  1312. public float Width
  1313. {
  1314. get
  1315. {
  1316. if (width == -1)
  1317. {
  1318. if (Renderer.HtmlTags)
  1319. {
  1320. width = 0;
  1321. foreach (Run run in Runs)
  1322. {
  1323. width += run.Width;
  1324. }
  1325. }
  1326. else
  1327. {
  1328. width = Renderer.Graphics.MeasureString(text, Renderer.Font, 10000, StringFormat.GenericTypographic).Width;
  1329. }
  1330. }
  1331. return width;
  1332. }
  1333. }
  1334. public float Top
  1335. {
  1336. get { return line.Top; }
  1337. }
  1338. public AdvancedTextRenderer Renderer
  1339. {
  1340. get { return line.Renderer; }
  1341. }
  1342. public List<Run> Runs
  1343. {
  1344. get { return runs; }
  1345. }
  1346. public float SpaceWidth
  1347. {
  1348. get
  1349. {
  1350. if (Runs == null || Runs.Count == 0)
  1351. return Renderer.SpaceWidth;
  1352. return Runs[Runs.Count - 1].SpaceWidth;
  1353. }
  1354. }
  1355. #endregion
  1356. #region Public Methods
  1357. public void AdjustRuns()
  1358. {
  1359. float left = Left;
  1360. foreach (Run run in Runs)
  1361. {
  1362. run.Left = left;
  1363. if (Renderer.RightToLeft)
  1364. {
  1365. left -= run.Width;
  1366. if (Renderer.PDFMode)
  1367. run.Left -= run.Width;
  1368. }
  1369. else
  1370. left += run.Width;
  1371. }
  1372. }
  1373. public void SetLine(Line line)
  1374. {
  1375. this.line = line;
  1376. }
  1377. public void Draw()
  1378. {
  1379. if (Renderer.HtmlTags)
  1380. {
  1381. foreach (Run run in Runs)
  1382. {
  1383. run.Draw();
  1384. }
  1385. }
  1386. else
  1387. {
  1388. // don't draw underlines & strikeouts because they are drawn in the Line.Draw method
  1389. Font font = Renderer.Font;
  1390. bool disposeFont = false;
  1391. if ((Renderer.Font.Style & FontStyle.Underline) > 0 || (Renderer.Font.Style & FontStyle.Strikeout) > 0)
  1392. {
  1393. font = new Font(Renderer.Font, Renderer.Font.Style & ~FontStyle.Underline & ~FontStyle.Strikeout);
  1394. disposeFont = true;
  1395. }
  1396. if (Renderer.OutlinePen == null)
  1397. {
  1398. Renderer.Graphics.DrawString(Text, font, Renderer.Brush, Left, Top, Renderer.Format);
  1399. }
  1400. else
  1401. {
  1402. GraphicsPath path = new GraphicsPath();
  1403. path.AddString(Text, font.FontFamily, Convert.ToInt32(font.Style), Renderer.Graphics.DpiY * font.Size / 72, new PointF(Left - 1, Top - 1), Renderer.Format);
  1404. Renderer.Graphics.FillAndDrawPath(Renderer.OutlinePen, Renderer.Brush, path);
  1405. }
  1406. if (disposeFont)
  1407. {
  1408. font.Dispose();
  1409. font = null;
  1410. }
  1411. }
  1412. }
  1413. internal float CalcHeight()
  1414. {
  1415. if (Renderer.HtmlTags)
  1416. {
  1417. float height = -1;
  1418. foreach (Run run in Runs)
  1419. {
  1420. height = Math.Max(height, run.Height);
  1421. }
  1422. if (height < 0)
  1423. height = Renderer.LineHeight;
  1424. return height;
  1425. }
  1426. else
  1427. {
  1428. #if SKIA
  1429. // we need actual height of a text because it may have font fallback with different metrics
  1430. if (!string.IsNullOrEmpty(text))
  1431. {
  1432. return DrawUtils.MeasureString(Renderer.Graphics.Graphics, text, Renderer.Font, Renderer.Format).Height;
  1433. }
  1434. #endif
  1435. return Renderer.LineHeight;
  1436. }
  1437. }
  1438. internal float CalcBaseLine()
  1439. {
  1440. float baseLine = 0;
  1441. if (Renderer.HtmlTags)
  1442. {
  1443. foreach (Run run in Runs)
  1444. {
  1445. baseLine = Math.Max(baseLine, run.CurrentBaseLine);
  1446. }
  1447. return baseLine;
  1448. }
  1449. else
  1450. {
  1451. return 0;
  1452. }
  1453. }
  1454. internal float CalcUnderBaseLine()
  1455. {
  1456. float underbaseLine = 0;
  1457. if (Renderer.HtmlTags)
  1458. {
  1459. foreach (Run run in Runs)
  1460. {
  1461. underbaseLine = Math.Max(underbaseLine, run.CurrentUnderBaseLine);
  1462. }
  1463. return underbaseLine;
  1464. }
  1465. else
  1466. {
  1467. return 0;
  1468. }
  1469. }
  1470. #endregion
  1471. public Word(string text, Line line)
  1472. {
  1473. this.text = text;
  1474. runs = new List<Run>();
  1475. this.line = line;
  1476. width = -1;
  1477. }
  1478. }
  1479. /// <summary>
  1480. /// Represents character placement.
  1481. /// </summary>
  1482. public enum BaseLine
  1483. {
  1484. Normal,
  1485. Subscript,
  1486. Superscript
  1487. }
  1488. /// <summary>
  1489. /// Represents a style used in HtmlTags mode.
  1490. /// </summary>
  1491. public class StyleDescriptor
  1492. {
  1493. #region Fields
  1494. private FontStyle fontStyle;
  1495. private Color color;
  1496. private BaseLine baseLine;
  1497. private string font;
  1498. private float size;
  1499. #endregion
  1500. #region Properties
  1501. public FontStyle FontStyle
  1502. {
  1503. get { return fontStyle; }
  1504. set { fontStyle = value; }
  1505. }
  1506. public string Font
  1507. {
  1508. get { return font; }
  1509. set { font = value; }
  1510. }
  1511. public float Size
  1512. {
  1513. get { return size; }
  1514. set { size = value; }
  1515. }
  1516. public Color Color
  1517. {
  1518. get { return color; }
  1519. set { color = value; }
  1520. }
  1521. public BaseLine BaseLine
  1522. {
  1523. get { return baseLine; }
  1524. set { baseLine = value; }
  1525. }
  1526. #endregion
  1527. #region Public Methods
  1528. public override string ToString()
  1529. {
  1530. string result = "";
  1531. if ((FontStyle & FontStyle.Bold) != 0)
  1532. result += "<b>";
  1533. if ((FontStyle & FontStyle.Italic) != 0)
  1534. result += "<i>";
  1535. if ((FontStyle & FontStyle.Underline) != 0)
  1536. result += "<u>";
  1537. if ((FontStyle & FontStyle.Strikeout) != 0)
  1538. result += "<strike>";
  1539. if (BaseLine == BaseLine.Subscript)
  1540. result += "<sub>";
  1541. if (BaseLine == BaseLine.Superscript)
  1542. result += "<sup>";
  1543. result += "<font color=\"";
  1544. if (ColorExt.IsKnownColor(Color))
  1545. result += Color.Name;
  1546. else
  1547. result += "#" + Color.ToArgb().ToString("x");
  1548. result += "\"";
  1549. if (Font != null)
  1550. result += " face=\"" + Font + "\"";
  1551. if (Size != 0)
  1552. result += " size=\"" + Math.Round(Size).ToString() + "\"";
  1553. result += ">";
  1554. return result;
  1555. }
  1556. #endregion
  1557. public StyleDescriptor(FontStyle fontStyle, Color color, BaseLine baseLine)
  1558. {
  1559. this.fontStyle = fontStyle;
  1560. this.color = color;
  1561. this.baseLine = baseLine;
  1562. }
  1563. }
  1564. /// <summary>
  1565. /// Represents sequence of characters that have the same <see cref="Style"/>.
  1566. /// </summary>
  1567. public class Run
  1568. {
  1569. #region Fields
  1570. protected string text;
  1571. private StyleDescriptor style;
  1572. protected Word word;
  1573. private float left;
  1574. protected float width;
  1575. protected float lineHeight;
  1576. protected float fontLineHeight;
  1577. private float baseLine;
  1578. protected float underBaseLine;
  1579. protected float spaceWidth;
  1580. #endregion
  1581. #region Properties
  1582. public string Text
  1583. {
  1584. get { return text; }
  1585. }
  1586. public StyleDescriptor Style
  1587. {
  1588. get { return style; }
  1589. }
  1590. public AdvancedTextRenderer Renderer
  1591. {
  1592. get { return word.Renderer; }
  1593. }
  1594. public float Left
  1595. {
  1596. get { return left; }
  1597. set { left = value; }
  1598. }
  1599. public float LineHeight
  1600. {
  1601. get
  1602. {
  1603. if (lineHeight == 0)
  1604. {
  1605. if (style.Font == null && style.Size <= 0)
  1606. lineHeight = Renderer.LineHeight;
  1607. else
  1608. lineHeight = GetFont().GetHeight(Renderer.Graphics.Graphics);
  1609. }
  1610. return lineHeight;
  1611. }
  1612. }
  1613. virtual public float CurrentBaseLine
  1614. {
  1615. get
  1616. {
  1617. if (baseLine < 0)
  1618. {
  1619. Font ff = GetFont();
  1620. float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle);
  1621. float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle);
  1622. baseLine = FontLineHeight * ascent / lineSpace;
  1623. underBaseLine = FontLineHeight - baseLine;
  1624. }
  1625. return baseLine;
  1626. }
  1627. }
  1628. virtual public float CurrentUnderBaseLine
  1629. {
  1630. get
  1631. {
  1632. if (underBaseLine < 0)
  1633. {
  1634. Font ff = GetFont();
  1635. float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle);
  1636. float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle);
  1637. baseLine = FontLineHeight * ascent / lineSpace;
  1638. underBaseLine = FontLineHeight - baseLine;
  1639. }
  1640. return baseLine;
  1641. }
  1642. }
  1643. public float FontLineHeight
  1644. {
  1645. get
  1646. {
  1647. if (fontLineHeight == 0)
  1648. {
  1649. if (style.Font == null && style.Size <= 0)
  1650. fontLineHeight = Renderer.FontLineHeight;
  1651. else
  1652. fontLineHeight = GetFont().GetHeight(Renderer.Graphics.Graphics);
  1653. }
  1654. return fontLineHeight;
  1655. }
  1656. }
  1657. public virtual float Top
  1658. {
  1659. get
  1660. {
  1661. float baseLine = 0;
  1662. if (Style.BaseLine == BaseLine.Subscript)
  1663. baseLine += FontLineHeight * 0.45f;
  1664. else if (Style.BaseLine == BaseLine.Superscript)
  1665. baseLine -= FontLineHeight * 0.15f;
  1666. return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine;
  1667. }
  1668. }
  1669. virtual public float Width
  1670. {
  1671. get { return width; }
  1672. }
  1673. virtual public float Height
  1674. {
  1675. get
  1676. {
  1677. return LineHeight;
  1678. }
  1679. }
  1680. public float SpaceWidth
  1681. {
  1682. get
  1683. {
  1684. if (spaceWidth < 0)
  1685. {
  1686. spaceWidth = CalculateSpaceSize(Renderer.Graphics, GetFont());// Renderer.Graphics.MeasureString(" ", GetFont()).Width;
  1687. }
  1688. return spaceWidth;
  1689. }
  1690. }
  1691. #endregion
  1692. #region Private Methods
  1693. private Font GetFont(bool disableUnderlinesStrikeouts)
  1694. {
  1695. float fontSize = Renderer.Font.Size;
  1696. if (Style.Size != 0)
  1697. fontSize = Style.Size;
  1698. if (Style.BaseLine != BaseLine.Normal)
  1699. fontSize *= 0.6f;
  1700. FontStyle fontStyle = Style.FontStyle;
  1701. if (disableUnderlinesStrikeouts)
  1702. fontStyle = fontStyle & ~FontStyle.Underline & ~FontStyle.Strikeout;
  1703. if (Style.Font != null)
  1704. return new Font(Style.Font, fontSize, fontStyle);
  1705. return new Font(Renderer.Font.FontFamily, fontSize, fontStyle);
  1706. }
  1707. #endregion
  1708. #region Public Methods
  1709. public Font GetFont()
  1710. {
  1711. return GetFont(false);
  1712. }
  1713. public Brush GetBrush()
  1714. {
  1715. return new SolidBrush(Style.Color);
  1716. }
  1717. public virtual void Draw()
  1718. {
  1719. using (Font font = GetFont(true))
  1720. using (Brush brush = GetBrush())
  1721. {
  1722. Renderer.Graphics.DrawString(text, font, brush, Left, Top, Renderer.Format);
  1723. }
  1724. }
  1725. #endregion
  1726. public Run(string text, StyleDescriptor style, Word word)
  1727. {
  1728. baseLine = float.MinValue;
  1729. underBaseLine = float.MinValue;
  1730. this.text = text;
  1731. this.style = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine);
  1732. this.style.Font = style.Font;
  1733. this.style.Size = style.Size;
  1734. this.word = word;
  1735. spaceWidth = -1;
  1736. using (Font font = GetFont())
  1737. {
  1738. width = Renderer.Graphics.MeasureString(text, font, 10000, StringFormat.GenericTypographic).Width;
  1739. }
  1740. }
  1741. }
  1742. /// <summary>
  1743. /// Represents inline Image.
  1744. /// </summary>
  1745. internal class RunImage : Run
  1746. {
  1747. public Image Image { get { return image; } }
  1748. override public float Width
  1749. {
  1750. get
  1751. {
  1752. if (Image == null) return base.Width;
  1753. return Image.Width;
  1754. }
  1755. }
  1756. override public float Top
  1757. {
  1758. get
  1759. {
  1760. float baseLine = 0;
  1761. if (Style.BaseLine == BaseLine.Subscript)
  1762. baseLine += FontLineHeight * 0.45f;
  1763. else if (Style.BaseLine == BaseLine.Superscript)
  1764. baseLine -= FontLineHeight * 0.15f;
  1765. return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine;
  1766. }
  1767. }
  1768. override public float CurrentBaseLine
  1769. {
  1770. get
  1771. {
  1772. if (Image == null) return base.CurrentBaseLine;
  1773. return Image.Height;
  1774. }
  1775. }
  1776. override public float Height
  1777. {
  1778. get
  1779. {
  1780. if (Image == null) return base.Height;
  1781. return Image.Height + word.line.CalcUnderBaseLine();
  1782. }
  1783. }
  1784. private Image image;
  1785. override public void Draw()
  1786. {
  1787. if (Image == null)
  1788. {
  1789. base.Draw();
  1790. return;
  1791. }
  1792. Renderer.Graphics.DrawImage(Image, Left, Top);// (FText, font, brush, Left, Top, Renderer.Format);
  1793. }
  1794. public static Bitmap ResizeImage(Image image, float scale)
  1795. {
  1796. int width = (int)(image.Width * scale);
  1797. int height = (int)(image.Height * scale);
  1798. if (width == 0) width = 1;
  1799. if (height == 0) height = 1;
  1800. Rectangle destRect = new Rectangle(0, 0, width, height);
  1801. Bitmap destImage = new Bitmap(width, height);
  1802. destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
  1803. using (Graphics graphics = System.Drawing.Graphics.FromImage(destImage))
  1804. {
  1805. graphics.CompositingMode = CompositingMode.SourceCopy;
  1806. graphics.CompositingQuality = CompositingQuality.HighQuality;
  1807. graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
  1808. graphics.SmoothingMode = SmoothingMode.HighQuality;
  1809. graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
  1810. using (System.Drawing.Imaging.ImageAttributes wrapMode = new System.Drawing.Imaging.ImageAttributes())
  1811. {
  1812. wrapMode.SetWrapMode(WrapMode.TileFlipXY);
  1813. graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
  1814. }
  1815. }
  1816. return destImage;
  1817. }
  1818. public RunImage(string src, string text, StyleDescriptor style, Word word) : base(text, style, word)
  1819. {
  1820. underBaseLine = 0;
  1821. image = ResizeImage(InlineImageCache.Load(Renderer.Cache, src), Renderer.Scale);
  1822. }
  1823. }
  1824. }
  1825. /// <summary>
  1826. /// Standard text renderer uses standard DrawString method to draw text. It also supports:
  1827. /// - text rotation;
  1828. /// - fonts with non-standard width ratio.
  1829. /// In case your text is justified, or contains html tags, use the <see cref="AdvancedTextRenderer"/>
  1830. /// class instead.
  1831. /// </summary>
  1832. internal class StandardTextRenderer
  1833. {
  1834. public static void Draw(string text, IGraphics g, Font font, Brush brush, Pen outlinePen,
  1835. RectangleF rect, StringFormat format, int angle, float widthRatio)
  1836. {
  1837. IGraphicsState state = g.Save();
  1838. g.SetClip(rect, CombineMode.Intersect);
  1839. g.TranslateTransform(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2);
  1840. g.RotateTransform(angle);
  1841. rect.X = -rect.Width / 2;
  1842. rect.Y = -rect.Height / 2;
  1843. if ((angle >= 90 && angle < 180) || (angle >= 270 && angle < 360))
  1844. rect = new RectangleF(rect.Y, rect.X, rect.Height, rect.Width);
  1845. g.ScaleTransform(widthRatio, 1);
  1846. rect.X /= widthRatio;
  1847. rect.Width /= widthRatio;
  1848. if (outlinePen == null)
  1849. {
  1850. g.DrawString(text, font, brush, rect, format);
  1851. }
  1852. else
  1853. {
  1854. GraphicsPath path = new GraphicsPath();
  1855. path.AddString(text, font.FontFamily, Convert.ToInt32(font.Style), g.DpiY * font.Size / 72, rect, format);
  1856. g.FillAndDrawPath(outlinePen, brush, path);
  1857. }
  1858. g.Restore(state);
  1859. }
  1860. }
  1861. /// <summary>
  1862. /// Cache for rendering img tags in textobject.
  1863. /// You can use only HTTP[s] protocol with absolute urls.
  1864. /// </summary>
  1865. public class InlineImageCache : IDisposable
  1866. {
  1867. #region Private Fields
  1868. private WebClient client;
  1869. private Dictionary<string, CacheItem> items;
  1870. private bool serialized;
  1871. private object locker;
  1872. #endregion Private Fields
  1873. #region Public Properties
  1874. /// <summary>
  1875. /// Is serialized
  1876. /// </summary>
  1877. public bool Serialized { get { return serialized; } set { serialized = value; } }
  1878. #endregion Public Properties
  1879. #region Private Properties
  1880. /// <summary>
  1881. /// Get or set WebClient for downloading imgs by url
  1882. /// </summary>
  1883. private WebClient Client
  1884. {
  1885. get
  1886. {
  1887. if (client == null)
  1888. {
  1889. client = new WebClient();
  1890. }
  1891. return client;
  1892. }
  1893. set
  1894. {
  1895. client = value;
  1896. }
  1897. }
  1898. #endregion Private Properties
  1899. #region Public Events
  1900. /// <summary>
  1901. /// Occurs before image load
  1902. /// </summary>
  1903. public static event EventHandler<LoadEventArgs> AfterLoad;
  1904. /// <summary>
  1905. /// Occurs after image load
  1906. /// </summary>
  1907. public static event EventHandler<LoadEventArgs> BeforeLoad;
  1908. #endregion Public Events
  1909. #region Public Methods
  1910. /// <summary>
  1911. /// Enumerates all values
  1912. /// </summary>
  1913. /// <returns></returns>
  1914. public IEnumerable<CacheItem> AllItems()
  1915. {
  1916. List<CacheItem> list = new List<CacheItem>();
  1917. lock (locker)
  1918. {
  1919. if (items != null)
  1920. {
  1921. foreach (KeyValuePair<string, CacheItem> item in items)
  1922. {
  1923. item.Value.Src = item.Key;
  1924. list.Add(item.Value);
  1925. }
  1926. }
  1927. }
  1928. return list;
  1929. }
  1930. /// <summary>
  1931. /// Return CacheItem by src
  1932. /// </summary>
  1933. /// <param name="src">Src attribute from img tag</param>
  1934. /// <returns></returns>
  1935. public CacheItem Get(string src)
  1936. {
  1937. CacheItem item = null;
  1938. if (!Validate(src))
  1939. item = new CacheItem();
  1940. if (String.IsNullOrEmpty(src))
  1941. return item;
  1942. lock (locker)
  1943. {
  1944. if (items == null)
  1945. {
  1946. items = new Dictionary<string, CacheItem>();
  1947. if (item == null)
  1948. item = new CacheItem();
  1949. items[src] = item;
  1950. Serialized = false;
  1951. }
  1952. if (items.ContainsKey(src))
  1953. return items[src];
  1954. }
  1955. return item;
  1956. }
  1957. /// <summary>
  1958. ///
  1959. /// </summary>
  1960. /// <param name="src"></param>
  1961. /// <returns></returns>
  1962. public Image Load(string src)
  1963. {
  1964. CacheItem item = null;
  1965. if (String.IsNullOrEmpty(src))
  1966. item = new CacheItem();
  1967. else
  1968. lock (locker)
  1969. {
  1970. if (items == null)
  1971. items = new Dictionary<string, CacheItem>();
  1972. else
  1973. if (items.ContainsKey(src))
  1974. return items[src].Image;
  1975. item = new CacheItem();
  1976. if (Validate(src))
  1977. {
  1978. try
  1979. {
  1980. if (src.StartsWith("data:"))
  1981. {
  1982. item.Set(src.Substring(src.IndexOf("base64,") + "base64,".Length));
  1983. }
  1984. else
  1985. item.Set(Client.DownloadData(src));
  1986. }
  1987. catch
  1988. {
  1989. item.Set("");
  1990. }
  1991. }
  1992. items[src] = item;
  1993. Serialized = false;
  1994. }
  1995. item.Src = src;
  1996. return item.Image;
  1997. }
  1998. /// <summary>
  1999. /// Set CacheItem by src
  2000. /// </summary>
  2001. /// <param name="src">Src attribute from img tag</param>
  2002. /// <param name="item">CacheItem</param>
  2003. /// <returns></returns>
  2004. public CacheItem Set(string src, CacheItem item)
  2005. {
  2006. if (String.IsNullOrEmpty(src))
  2007. return new CacheItem();
  2008. lock (locker)
  2009. {
  2010. if (items == null)
  2011. items = new Dictionary<string, CacheItem>();
  2012. if (!Validate(src))
  2013. item = new CacheItem();
  2014. items[src] = item;
  2015. Serialized = false;
  2016. }
  2017. item.Src = src;
  2018. return item;
  2019. }
  2020. /// <summary>
  2021. /// Validate src attribute from image
  2022. /// </summary>
  2023. /// <param name="src">Src attribute from img tag</param>
  2024. /// <returns>return true if src is valid</returns>
  2025. public bool Validate(string src)
  2026. {
  2027. if (String.IsNullOrEmpty(src))
  2028. return false;
  2029. src = src.ToLower();
  2030. if (src.StartsWith("http://"))
  2031. return true;
  2032. if (src.StartsWith("https://"))
  2033. return true;
  2034. if (src.StartsWith("data:") && src.IndexOf("base64,") > 0)
  2035. return true;
  2036. return false;
  2037. }
  2038. #endregion Public Methods
  2039. #region Internal Methods
  2040. static internal Image Load(InlineImageCache cache, string src)
  2041. {
  2042. LoadEventArgs args = new LoadEventArgs(cache, src);
  2043. if (BeforeLoad != null) BeforeLoad(null, args);
  2044. Image result = null;
  2045. if (!args.Handled) result = cache.Load(src);
  2046. args.Handled = false;
  2047. if (AfterLoad != null) AfterLoad(null, args);
  2048. if (args.Handled)
  2049. return cache.Get(src).Image;
  2050. return result;
  2051. }
  2052. #endregion Internal Methods
  2053. #region Public Constructors
  2054. /// <inheritdoc/>
  2055. public InlineImageCache()
  2056. {
  2057. locker = new object();
  2058. client = null;
  2059. }
  2060. /// <summary>
  2061. ///
  2062. /// </summary>
  2063. ~InlineImageCache()
  2064. {
  2065. Dispose(false);
  2066. }
  2067. #endregion Public Constructors
  2068. #region Public Classes
  2069. /// <summary>
  2070. /// Item of image cache Dictionary
  2071. /// </summary>
  2072. public class CacheItem : IDisposable
  2073. {
  2074. #region Private Fields
  2075. private string base64;
  2076. private bool error;
  2077. private Image image;
  2078. //private int FId;
  2079. private string src;
  2080. private byte[] stream;
  2081. #endregion Private Fields
  2082. #region Public Properties
  2083. /// <summary>
  2084. /// Get Base64 string
  2085. /// </summary>
  2086. public string Base64
  2087. {
  2088. get
  2089. {
  2090. try
  2091. {//For strange img tag
  2092. if (base64 != null)
  2093. return base64;
  2094. if (stream != null)
  2095. {
  2096. base64 = Convert.ToBase64String(stream);
  2097. return base64;
  2098. }
  2099. if (image != null)
  2100. {
  2101. using (MemoryStream ms = new MemoryStream())
  2102. {
  2103. image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
  2104. ms.Flush();
  2105. stream = ms.ToArray();
  2106. }
  2107. base64 = Convert.ToBase64String(stream);
  2108. return base64;
  2109. }
  2110. }
  2111. catch { }
  2112. GetErrorImage();
  2113. return "";
  2114. }
  2115. }
  2116. /// <summary>
  2117. /// Return true if has some error with Image
  2118. /// </summary>
  2119. public bool Error
  2120. {
  2121. get { return error; }
  2122. }
  2123. /// <summary>
  2124. /// Get Image
  2125. /// </summary>
  2126. public Image Image
  2127. {
  2128. get
  2129. {
  2130. try
  2131. {//for strange img tag
  2132. if (image != null)
  2133. return image;
  2134. if (stream != null)
  2135. {
  2136. MemoryStream ms = new MemoryStream(stream);
  2137. image = Bitmap.FromStream(ms);
  2138. return image;
  2139. }
  2140. if (base64 != null)
  2141. {
  2142. this.stream = Convert.FromBase64String(base64);
  2143. MemoryStream ms = new MemoryStream(stream);
  2144. image = Bitmap.FromStream(ms);
  2145. return image;
  2146. }
  2147. }
  2148. catch { }
  2149. return GetErrorImage();
  2150. }
  2151. }
  2152. /// <summary>
  2153. /// Get byte array
  2154. /// </summary>
  2155. public byte[] Stream
  2156. {
  2157. get
  2158. {
  2159. if (stream != null) return stream;
  2160. if (base64 != null)
  2161. {
  2162. stream = Convert.FromBase64String(base64);
  2163. return stream;
  2164. }
  2165. if (image != null)
  2166. {
  2167. using (MemoryStream ms = new MemoryStream())
  2168. {
  2169. image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
  2170. ms.Flush();
  2171. stream = ms.ToArray();
  2172. }
  2173. return stream;
  2174. }
  2175. return new byte[0];
  2176. }
  2177. }
  2178. #endregion Public Properties
  2179. #region Internal Properties
  2180. internal string Src
  2181. {
  2182. get { return src; }
  2183. set { src = value; }
  2184. }
  2185. #endregion Internal Properties
  2186. #region Public Methods
  2187. /// <summary>
  2188. /// Return error image and set true to error property
  2189. /// </summary>
  2190. /// <returns></returns>
  2191. public Image GetErrorImage()
  2192. {
  2193. error = true;
  2194. base64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFdJREFUOE9jbGlq+c9AIqipq2GEawEZQAo4dvgYqoXD0QAGhv9ATyKCBY1PXBjANKEbBjSWOANA9mPRDBImzgCKXECVMMCTsojzwtAzAOQvUjCJmRe/cgDt6ZAkZx23LwAAAABJRU5ErkJggg==";
  2195. src = "data:image/png;base64," + base64;
  2196. stream = Convert.FromBase64String(base64);
  2197. using (MemoryStream ms = new MemoryStream(stream))
  2198. image = Bitmap.FromStream(ms);
  2199. return image;
  2200. }
  2201. /// <summary>
  2202. /// Set value for cache item
  2203. /// </summary>
  2204. /// <param name="base64">Image encoded base64 string</param>
  2205. public void Set(string base64)
  2206. {
  2207. this.base64 = base64;
  2208. image = null;
  2209. stream = null;
  2210. }
  2211. /// <summary>
  2212. /// Set value for cache item
  2213. /// </summary>
  2214. /// <param name="img">Image</param>
  2215. public void Set(Image img)
  2216. {
  2217. base64 = null;
  2218. image = img;
  2219. stream = null;
  2220. }
  2221. /// <summary>
  2222. /// Set value for cache item
  2223. /// </summary>
  2224. /// <param name="arr">Image</param>
  2225. public void Set(byte[] arr)
  2226. {
  2227. base64 = null;
  2228. image = null;
  2229. stream = arr;
  2230. }
  2231. #region IDisposable Support
  2232. private bool disposedValue = false; // To detect redundant calls
  2233. /// <summary>
  2234. ///
  2235. /// </summary>
  2236. /// <param name="disposing"></param>
  2237. protected virtual void Dispose(bool disposing)
  2238. {
  2239. if (!disposedValue)
  2240. {
  2241. if (disposing)
  2242. {
  2243. if (image != null)
  2244. {
  2245. image.Dispose();
  2246. image = null;
  2247. }
  2248. // TODO: dispose managed state (managed objects).
  2249. }
  2250. // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
  2251. // TODO: set large fields to null.
  2252. disposedValue = true;
  2253. }
  2254. }
  2255. // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  2256. // ~CacheItem() {
  2257. // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  2258. // Dispose(false);
  2259. // }
  2260. // This code added to correctly implement the disposable pattern.
  2261. public void Dispose()
  2262. {
  2263. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  2264. Dispose(true);
  2265. // TODO: uncomment the following line if the finalizer is overridden above.
  2266. // GC.SuppressFinalize(this);
  2267. }
  2268. #endregion
  2269. #endregion Public Methods
  2270. }
  2271. /// <summary>
  2272. /// WebClientEventArgs
  2273. /// </summary>
  2274. public class LoadEventArgs : EventArgs
  2275. {
  2276. #region Private Fields
  2277. private InlineImageCache cache;
  2278. private bool handled;
  2279. private string source;
  2280. #endregion Private Fields
  2281. #region Public Properties
  2282. /// <summary>
  2283. /// Gets a cache
  2284. /// </summary>
  2285. public InlineImageCache Cache { get { return cache; } }
  2286. /// <summary>
  2287. /// Gets or sets a value indicating whether the event was handled.
  2288. /// </summary>
  2289. public bool Handled { get { return handled; } set { handled = value; } }
  2290. /// <summary>
  2291. /// Gets or sets a url from src attribue of img tag
  2292. /// </summary>
  2293. public string Source { get { return source; } set { source = value; } }
  2294. #endregion Public Properties
  2295. #region Internal Constructors
  2296. internal LoadEventArgs(InlineImageCache c, string src)
  2297. {
  2298. cache = c;
  2299. source = src;
  2300. handled = false;
  2301. }
  2302. #endregion Internal Constructors
  2303. }
  2304. #region IDisposable Support
  2305. private bool disposedValue = false; // To detect redundant calls
  2306. /// <summary>
  2307. ///
  2308. /// </summary>
  2309. /// <param name="disposing"></param>
  2310. protected virtual void Dispose(bool disposing)
  2311. {
  2312. if (!disposedValue)
  2313. {
  2314. if (disposing)
  2315. {
  2316. // TODO: dispose managed state (managed objects).
  2317. }
  2318. if (this.items != null)
  2319. {
  2320. Dictionary<string, CacheItem> items = this.items;
  2321. this.items = null;
  2322. foreach (CacheItem item in items.Values)
  2323. item.Dispose();
  2324. }
  2325. // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
  2326. // TODO: set large fields to null.
  2327. disposedValue = true;
  2328. }
  2329. }
  2330. // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  2331. // ~InlineImageCache() {
  2332. // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  2333. // Dispose(false);
  2334. // }
  2335. // This code added to correctly implement the disposable pattern.
  2336. public void Dispose()
  2337. {
  2338. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  2339. Dispose(true);
  2340. // TODO: uncomment the following line if the finalizer is overridden above.
  2341. // GC.SuppressFinalize(this);
  2342. }
  2343. #endregion
  2344. #endregion Public Classes
  2345. }
  2346. }