TextRenderer.cs 92 KB

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