SvgTextBase.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Text.RegularExpressions;
  5. using System.ComponentModel;
  6. using System.Drawing;
  7. using System.Drawing.Drawing2D;
  8. using System.Drawing.Text;
  9. using Svg.DataTypes;
  10. using System.Linq;
  11. #pragma warning disable
  12. namespace Svg
  13. {
  14. public abstract class SvgTextBase : SvgVisualElement
  15. {
  16. [CLSCompliant(false)] protected SvgUnitCollection _x = new SvgUnitCollection();
  17. [CLSCompliant(false)] protected SvgUnitCollection _y = new SvgUnitCollection();
  18. [CLSCompliant(false)] protected SvgUnitCollection _dy = new SvgUnitCollection();
  19. [CLSCompliant(false)] protected SvgUnitCollection _dx = new SvgUnitCollection();
  20. private string _rotate;
  21. private List<float> _rotations = new List<float>();
  22. /// <summary>
  23. /// Gets or sets the text to be rendered.
  24. /// </summary>
  25. public virtual string Text
  26. {
  27. get { return base.Content; }
  28. set {
  29. Nodes.Clear();
  30. Children.Clear();
  31. if(value != null)
  32. {
  33. Nodes.Add(new SvgContentNode { Content = value });
  34. }
  35. this.IsPathDirty = true;
  36. Content = value;
  37. }
  38. }
  39. public override XmlSpaceHandling SpaceHandling
  40. {
  41. get { return base.SpaceHandling; }
  42. set { base.SpaceHandling = value; this.IsPathDirty = true; }
  43. }
  44. /// <summary>
  45. /// Gets or sets the X.
  46. /// </summary>
  47. /// <value>The X.</value>
  48. [SvgAttribute("x")]
  49. public virtual SvgUnitCollection X
  50. {
  51. get { return this._x; }
  52. set
  53. {
  54. if (_x != value)
  55. {
  56. this._x = value;
  57. this.IsPathDirty = true;
  58. OnAttributeChanged(new AttributeEventArgs { Attribute = "x", Value = value });
  59. }
  60. }
  61. }
  62. /// <summary>
  63. /// Gets or sets the dX.
  64. /// </summary>
  65. /// <value>The dX.</value>
  66. [SvgAttribute("dx")]
  67. public virtual SvgUnitCollection Dx
  68. {
  69. get { return this._dx; }
  70. set
  71. {
  72. if (_dx != value)
  73. {
  74. this._dx = value;
  75. this.IsPathDirty = true;
  76. OnAttributeChanged(new AttributeEventArgs { Attribute = "dx", Value = value });
  77. }
  78. }
  79. }
  80. /// <summary>
  81. /// Gets or sets the Y.
  82. /// </summary>
  83. /// <value>The Y.</value>
  84. [SvgAttribute("y")]
  85. public virtual SvgUnitCollection Y
  86. {
  87. get { return this._y; }
  88. set
  89. {
  90. if (_y != value)
  91. {
  92. this._y = value;
  93. this.IsPathDirty = true;
  94. OnAttributeChanged(new AttributeEventArgs { Attribute = "y", Value = value });
  95. }
  96. }
  97. }
  98. /// <summary>
  99. /// Gets or sets the dY.
  100. /// </summary>
  101. /// <value>The dY.</value>
  102. [SvgAttribute("dy")]
  103. public virtual SvgUnitCollection Dy
  104. {
  105. get { return this._dy; }
  106. set
  107. {
  108. if (_dy != value)
  109. {
  110. this._dy = value;
  111. this.IsPathDirty = true;
  112. OnAttributeChanged(new AttributeEventArgs { Attribute = "dy", Value = value });
  113. }
  114. }
  115. }
  116. /// <summary>
  117. /// Gets or sets the rotate.
  118. /// </summary>
  119. /// <value>The rotate.</value>
  120. [SvgAttribute("rotate")]
  121. public virtual string Rotate
  122. {
  123. get { return this._rotate; }
  124. set
  125. {
  126. if (_rotate != value)
  127. {
  128. this._rotate = value;
  129. this._rotations.Clear();
  130. this._rotations.AddRange(from r in _rotate.Split(new char[] { ',', ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries) select float.Parse(r));
  131. this.IsPathDirty = true;
  132. OnAttributeChanged(new AttributeEventArgs { Attribute = "rotate", Value = value });
  133. }
  134. }
  135. }
  136. /// <summary>
  137. /// The pre-calculated length of the text
  138. /// </summary>
  139. [SvgAttribute("textLength", true)]
  140. public virtual SvgUnit TextLength
  141. {
  142. get { return (this.Attributes["textLength"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["textLength"]); }
  143. set { this.Attributes["textLength"] = value; this.IsPathDirty = true; }
  144. }
  145. /// <summary>
  146. /// Gets or sets the text anchor.
  147. /// </summary>
  148. /// <value>The text anchor.</value>
  149. [SvgAttribute("lengthAdjust", true)]
  150. public virtual SvgTextLengthAdjust LengthAdjust
  151. {
  152. get { return (this.Attributes["lengthAdjust"] == null) ? SvgTextLengthAdjust.Spacing : (SvgTextLengthAdjust)this.Attributes["lengthAdjust"]; }
  153. set { this.Attributes["lengthAdjust"] = value; this.IsPathDirty = true; }
  154. }
  155. /// <summary>
  156. /// Specifies spacing behavior between text characters.
  157. /// </summary>
  158. [SvgAttribute("letter-spacing", true)]
  159. public virtual SvgUnit LetterSpacing
  160. {
  161. get { return (this.Attributes["letter-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["letter-spacing"]); }
  162. set { this.Attributes["letter-spacing"] = value; this.IsPathDirty = true; }
  163. }
  164. /// <summary>
  165. /// Specifies spacing behavior between words.
  166. /// </summary>
  167. [SvgAttribute("word-spacing", true)]
  168. public virtual SvgUnit WordSpacing
  169. {
  170. get { return (this.Attributes["word-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["word-spacing"]); }
  171. set { this.Attributes["word-spacing"] = value; this.IsPathDirty = true; }
  172. }
  173. /// <summary>
  174. /// Gets or sets the fill.
  175. /// </summary>
  176. /// <remarks>
  177. /// <para>Unlike other <see cref="SvgGraphicsElement"/>s, <see cref="SvgText"/> has a default fill of black rather than transparent.</para>
  178. /// </remarks>
  179. /// <value>The fill.</value>
  180. public override SvgPaintServer Fill
  181. {
  182. get { return (this.Attributes["fill"] == null) ? new SvgColourServer(System.Drawing.Color.Black) : (SvgPaintServer)this.Attributes["fill"]; }
  183. set { this.Attributes["fill"] = value; }
  184. }
  185. /// <summary>
  186. /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
  187. /// </summary>
  188. /// <returns>
  189. /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
  190. /// </returns>
  191. public override string ToString()
  192. {
  193. return this.Text;
  194. }
  195. /// <summary>
  196. /// Gets the bounds of the element.
  197. /// </summary>
  198. /// <value>The bounds.</value>
  199. public override System.Drawing.RectangleF Bounds
  200. {
  201. get
  202. {
  203. var path = this.Path(null);
  204. foreach (var elem in this.Children.OfType<SvgVisualElement>())
  205. {
  206. path.AddPath(elem.Path(null), false);
  207. }
  208. return path.GetBounds();
  209. }
  210. }
  211. /// <summary>
  212. /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
  213. /// </summary>
  214. /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
  215. /// <remarks>Necessary to make sure that any internal tspan elements get rendered as well</remarks>
  216. protected override void Render(ISvgRenderer renderer)
  217. {
  218. if ((this.Path(renderer) != null) && this.Visible && this.Displayable)
  219. {
  220. this.PushTransforms(renderer);
  221. this.SetClip(renderer);
  222. // If this element needs smoothing enabled turn anti-aliasing on
  223. if (this.RequiresSmoothRendering)
  224. {
  225. renderer.SmoothingMode = SmoothingMode.AntiAlias;
  226. }
  227. this.RenderFill(renderer);
  228. this.RenderStroke(renderer);
  229. this.RenderChildren(renderer);
  230. // Reset the smoothing mode
  231. if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias)
  232. {
  233. renderer.SmoothingMode = SmoothingMode.Default;
  234. }
  235. this.ResetClip(renderer);
  236. this.PopTransforms(renderer);
  237. }
  238. }
  239. internal virtual IEnumerable<ISvgNode> GetContentNodes()
  240. {
  241. return (this.Nodes == null || this.Nodes.Count < 1 ? this.Children.OfType<ISvgNode>().Where(o => !(o is ISvgDescriptiveElement)) : this.Nodes);
  242. }
  243. protected virtual GraphicsPath GetBaselinePath(ISvgRenderer renderer)
  244. {
  245. return null;
  246. }
  247. protected virtual float GetAuthorPathLength()
  248. {
  249. return 0;
  250. }
  251. private GraphicsPath _path;
  252. /// <summary>
  253. /// Gets the <see cref="GraphicsPath"/> for this element.
  254. /// </summary>
  255. /// <value></value>
  256. public override GraphicsPath Path(ISvgRenderer renderer)
  257. {
  258. //if there is a TSpan inside of this text element then path should not be null (even if this text is empty!)
  259. var nodes = GetContentNodes().Where(x => x is SvgContentNode &&
  260. string.IsNullOrEmpty(x.Content.Trim(new[] { '\r', '\n', '\t' })));
  261. if (_path == null || IsPathDirty || nodes.Count() == 1)
  262. {
  263. renderer = (renderer ?? SvgRenderer.FromNull());
  264. SetPath(new TextDrawingState(renderer, this));
  265. }
  266. return _path;
  267. }
  268. private void SetPath(TextDrawingState state)
  269. {
  270. SetPath(state, true);
  271. }
  272. /// <summary>
  273. /// Sets the path on this element and all child elements. Uses the state
  274. /// object to track the state of the drawing
  275. /// </summary>
  276. /// <param name="state">State of the drawing operation</param>
  277. private void SetPath(TextDrawingState state, bool doMeasurements)
  278. {
  279. TextDrawingState origState = null;
  280. bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End);
  281. if (doMeasurements)
  282. {
  283. if (this.TextLength != SvgUnit.None)
  284. {
  285. origState = state.Clone();
  286. }
  287. else if (alignOnBaseline)
  288. {
  289. origState = state.Clone();
  290. state.BaselinePath = null;
  291. }
  292. }
  293. foreach (var node in GetContentNodes())
  294. {
  295. SvgTextBase textNode = node as SvgTextBase;
  296. if (textNode == null)
  297. {
  298. if (!string.IsNullOrEmpty(node.Content)) state.DrawString(PrepareText(node.Content));
  299. }
  300. else
  301. {
  302. TextDrawingState newState= new TextDrawingState(state, textNode);
  303. textNode.SetPath(newState);
  304. state.NumChars += newState.NumChars;
  305. state.Current = newState.Current;
  306. }
  307. }
  308. var path = state.GetPath() ?? new GraphicsPath();
  309. // Apply any text length adjustments
  310. if (doMeasurements)
  311. {
  312. if (this.TextLength != SvgUnit.None)
  313. {
  314. var bounds = path.GetBounds();
  315. var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this);
  316. var actLength = bounds.Width;
  317. var diff = (actLength - specLength);
  318. if (Math.Abs(diff) > 1.5)
  319. {
  320. if (this.LengthAdjust == SvgTextLengthAdjust.Spacing)
  321. {
  322. origState.LetterSpacingAdjust = -1 * diff / (state.NumChars - origState.NumChars - 1);
  323. SetPath(origState, false);
  324. return;
  325. }
  326. else
  327. {
  328. using (var matrix = new Matrix())
  329. {
  330. matrix.Translate(-1 * bounds.X, 0, MatrixOrder.Append);
  331. matrix.Scale(specLength / actLength, 1, MatrixOrder.Append);
  332. matrix.Translate(bounds.X, 0, MatrixOrder.Append);
  333. path.Transform(matrix);
  334. }
  335. }
  336. }
  337. }
  338. else if (alignOnBaseline)
  339. {
  340. var bounds = path.GetBounds();
  341. if (this.TextAnchor == SvgTextAnchor.Middle)
  342. {
  343. origState.StartOffsetAdjust = -1 * bounds.Width / 2;
  344. }
  345. else
  346. {
  347. origState.StartOffsetAdjust = -1 * bounds.Width;
  348. }
  349. SetPath(origState, false);
  350. return;
  351. }
  352. }
  353. _path = path;
  354. this.IsPathDirty = false;
  355. }
  356. private static readonly Regex MultipleSpaces = new Regex(@" {2,}", RegexOptions.Compiled);
  357. /// <summary>
  358. /// Prepare the text according to the whitespace handling rules. <see href="http://www.w3.org/TR/SVG/text.html">SVG Spec</see>.
  359. /// </summary>
  360. /// <param name="value">Text to be prepared</param>
  361. /// <returns>Prepared text</returns>
  362. protected string PrepareText(string value)
  363. {
  364. if (this.SpaceHandling == XmlSpaceHandling.preserve)
  365. {
  366. return value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' ');
  367. }
  368. else
  369. {
  370. var convValue = MultipleSpaces.Replace(value.Replace("\r", "").Replace("\n", "").Replace('\t', ' '), " ");
  371. return convValue;
  372. }
  373. }
  374. [SvgAttribute("onchange")]
  375. public event EventHandler<StringArg> Change;
  376. //change
  377. protected void OnChange(string newString, string sessionID)
  378. {
  379. RaiseChange(this, new StringArg { s = newString, SessionID = sessionID });
  380. }
  381. protected void RaiseChange(object sender, StringArg s)
  382. {
  383. var handler = Change;
  384. if (handler != null)
  385. {
  386. handler(sender, s);
  387. }
  388. }
  389. //private static GraphicsPath GetPath(string text, Font font)
  390. //{
  391. // var fontMetrics = (from c in text.Distinct()
  392. // select new { Char = c, Metrics = Metrics(c, font) }).
  393. // ToDictionary(c => c.Char, c=> c.Metrics);
  394. // // Measure each character and check the metrics against the overall metrics of rendering
  395. // // an entire word with kerning.
  396. //}
  397. //private static RectangleF Metrics(char c, Font font)
  398. //{
  399. // var path = new GraphicsPath();
  400. // path.AddString(c.ToString(), font.FontFamily, (int)font.Style, font.Size, new Point(0, 0), StringFormat.GenericTypographic);
  401. // return path.GetBounds();
  402. //}
  403. #if Net4
  404. public override void RegisterEvents(ISvgEventCaller caller)
  405. {
  406. //register basic events
  407. base.RegisterEvents(caller);
  408. //add change event for text
  409. caller.RegisterAction<string, string>(this.ID + "/onchange", OnChange);
  410. }
  411. public override void UnregisterEvents(ISvgEventCaller caller)
  412. {
  413. //unregister base events
  414. base.UnregisterEvents(caller);
  415. //unregister change event
  416. caller.UnregisterAction(this.ID + "/onchange");
  417. }
  418. #endif
  419. private class FontBoundable : ISvgBoundable
  420. {
  421. private IFontDefn _font;
  422. private float _width = 1;
  423. public FontBoundable(IFontDefn font)
  424. {
  425. _font = font;
  426. }
  427. public FontBoundable(IFontDefn font, float width)
  428. {
  429. _font = font;
  430. _width = width;
  431. }
  432. public PointF Location
  433. {
  434. get { return PointF.Empty; }
  435. }
  436. public SizeF Size
  437. {
  438. get { return new SizeF(_width, _font.Size); }
  439. }
  440. public RectangleF Bounds
  441. {
  442. get { return new RectangleF(this.Location, this.Size); }
  443. }
  444. }
  445. private class TextDrawingState
  446. {
  447. private float _xAnchor = float.MinValue;
  448. private IList<GraphicsPath> _anchoredPaths = new List<GraphicsPath>();
  449. private GraphicsPath _currPath = null;
  450. private GraphicsPath _finalPath = null;
  451. private float _authorPathLength = 0;
  452. public GraphicsPath BaselinePath { get; set; }
  453. public PointF Current { get; set; }
  454. public SvgTextBase Element { get; set; }
  455. public float LetterSpacingAdjust { get; set; }
  456. public int NumChars { get; set; }
  457. public TextDrawingState Parent { get; set; }
  458. public ISvgRenderer Renderer { get; set; }
  459. public float StartOffsetAdjust { get; set; }
  460. private TextDrawingState() { }
  461. public TextDrawingState(ISvgRenderer renderer, SvgTextBase element)
  462. {
  463. this.Element = element;
  464. this.Renderer = renderer;
  465. this.Current = PointF.Empty;
  466. _xAnchor = 0;
  467. this.BaselinePath = element.GetBaselinePath(renderer);
  468. _authorPathLength = element.GetAuthorPathLength();
  469. }
  470. public TextDrawingState(TextDrawingState parent, SvgTextBase element)
  471. {
  472. this.Element = element;
  473. this.Renderer = parent.Renderer;
  474. this.Parent = parent;
  475. this.Current = parent.Current;
  476. this.BaselinePath = element.GetBaselinePath(parent.Renderer) ?? parent.BaselinePath;
  477. var currPathLength = element.GetAuthorPathLength();
  478. _authorPathLength = currPathLength == 0 ? parent._authorPathLength : currPathLength;
  479. }
  480. public GraphicsPath GetPath()
  481. {
  482. FlushPath();
  483. return _finalPath;
  484. }
  485. public TextDrawingState Clone()
  486. {
  487. var result = new TextDrawingState();
  488. result._anchoredPaths = this._anchoredPaths.ToList();
  489. result.BaselinePath = this.BaselinePath;
  490. result._xAnchor = this._xAnchor;
  491. result.Current = this.Current;
  492. result.Element = this.Element;
  493. result.NumChars = this.NumChars;
  494. result.Parent = this.Parent;
  495. result.Renderer = this.Renderer;
  496. return result;
  497. }
  498. public void DrawString(string value)
  499. {
  500. // Get any defined anchors
  501. var xAnchors = GetValues(value.Length, e => e._x, UnitRenderingType.HorizontalOffset);
  502. var yAnchors = GetValues(value.Length, e => e._y, UnitRenderingType.VerticalOffset);
  503. using (var font = this.Element.GetFont(this.Renderer))
  504. {
  505. var fontBaselineHeight = font.Ascent(this.Renderer);
  506. PathStatistics pathStats = null;
  507. var pathScale = 1.0;
  508. if (BaselinePath != null)
  509. {
  510. pathStats = new PathStatistics(BaselinePath.PathData);
  511. if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength;
  512. }
  513. // Get all of the offsets (explicit and defined by spacing)
  514. IList<float> xOffsets;
  515. IList<float> yOffsets;
  516. IList<float> rotations;
  517. float baselineShift = 0.0f;
  518. try
  519. {
  520. this.Renderer.SetBoundable(new FontBoundable(font, (float)(pathStats == null ? 1 : pathStats.TotalLength)));
  521. xOffsets = GetValues(value.Length, e => e._dx, UnitRenderingType.Horizontal);
  522. yOffsets = GetValues(value.Length, e => e._dy, UnitRenderingType.Vertical);
  523. if (StartOffsetAdjust != 0.0f)
  524. {
  525. if (xOffsets.Count < 1)
  526. {
  527. xOffsets.Add(StartOffsetAdjust);
  528. }
  529. else
  530. {
  531. xOffsets[0] += StartOffsetAdjust;
  532. }
  533. }
  534. if (this.Element.LetterSpacing.Value != 0.0f || this.Element.WordSpacing.Value != 0.0f || this.LetterSpacingAdjust != 0.0f)
  535. {
  536. var spacing = this.Element.LetterSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element) + this.LetterSpacingAdjust;
  537. var wordSpacing = this.Element.WordSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element);
  538. if (this.Parent == null && this.NumChars == 0 && xOffsets.Count < 1) xOffsets.Add(0);
  539. for (int i = (this.Parent == null && this.NumChars == 0 ? 1 : 0); i < value.Length; i++)
  540. {
  541. if (i >= xOffsets.Count)
  542. {
  543. xOffsets.Add(spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0));
  544. }
  545. else
  546. {
  547. xOffsets[i] += spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0);
  548. }
  549. }
  550. }
  551. rotations = GetValues(value.Length, e => e._rotations);
  552. // Calculate Y-offset due to baseline shift. Don't inherit the value so that it is not accumulated multiple times.
  553. var baselineShiftText = this.Element.Attributes.GetAttribute<string>("baseline-shift");
  554. switch (baselineShiftText)
  555. {
  556. case null:
  557. case "":
  558. case "baseline":
  559. case "inherit":
  560. // do nothing
  561. break;
  562. case "sub":
  563. baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
  564. break;
  565. case "super":
  566. baselineShift = -1 * new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
  567. break;
  568. default:
  569. var convert = new SvgUnitConverter();
  570. var shiftUnit = (SvgUnit)convert.ConvertFromInvariantString(baselineShiftText);
  571. baselineShift = -1 * shiftUnit.ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
  572. break;
  573. }
  574. if (baselineShift != 0.0f)
  575. {
  576. if (yOffsets.Any())
  577. {
  578. yOffsets[0] += baselineShift;
  579. }
  580. else
  581. {
  582. yOffsets.Add(baselineShift);
  583. }
  584. }
  585. }
  586. finally
  587. {
  588. this.Renderer.PopBoundable();
  589. }
  590. // NOTE: Assuming a horizontal left-to-right font
  591. // Render absolutely positioned items in the horizontal direction
  592. var yPos = Current.Y;
  593. for (int i = 0; i < xAnchors.Count - 1; i++)
  594. {
  595. FlushPath();
  596. _xAnchor = xAnchors[i] + (xOffsets.Count > i ? xOffsets[i] : 0);
  597. EnsurePath();
  598. yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0);
  599. DrawStringOnCurrPath(value[i].ToString(), font, new PointF(_xAnchor, yPos),
  600. fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault()));
  601. }
  602. // Render any remaining characters
  603. var renderChar = 0;
  604. var xPos = this.Current.X;
  605. if (xAnchors.Any())
  606. {
  607. FlushPath();
  608. renderChar = xAnchors.Count - 1;
  609. xPos = xAnchors.Last();
  610. _xAnchor = xPos;
  611. }
  612. EnsurePath();
  613. // Render individual characters as necessary
  614. var lastIndividualChar = renderChar + Math.Max(Math.Max(Math.Max(Math.Max(xOffsets.Count, yOffsets.Count), yAnchors.Count), rotations.Count) - renderChar - 1, 0);
  615. if (rotations.LastOrDefault() != 0.0f || pathStats != null) lastIndividualChar = value.Length;
  616. if (lastIndividualChar > renderChar)
  617. {
  618. var charBounds = font.MeasureCharacters(this.Renderer, value.Substring(renderChar, Math.Min(lastIndividualChar + 1, value.Length) - renderChar));
  619. PointF pathPoint;
  620. float rotation;
  621. float halfWidth;
  622. for (int i = renderChar; i < lastIndividualChar; i++)
  623. {
  624. xPos += (float)pathScale * (xOffsets.Count > i ? xOffsets[i] : 0) + (charBounds[i - renderChar].X - (i == renderChar ? 0 : charBounds[i - renderChar - 1].X));
  625. yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0);
  626. if (pathStats == null)
  627. {
  628. DrawStringOnCurrPath(value[i].ToString(), font, new PointF(xPos, yPos),
  629. fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault()));
  630. }
  631. else
  632. {
  633. xPos = Math.Max(xPos, 0);
  634. halfWidth = charBounds[i - renderChar].Width / 2;
  635. if (pathStats.OffsetOnPath(xPos + halfWidth))
  636. {
  637. pathStats.LocationAngleAtOffset(xPos + halfWidth, out pathPoint, out rotation);
  638. pathPoint = new PointF((float)(pathPoint.X - halfWidth * Math.Cos(rotation * Math.PI / 180) - (float)pathScale * yPos * Math.Sin(rotation * Math.PI / 180)),
  639. (float)(pathPoint.Y - halfWidth * Math.Sin(rotation * Math.PI / 180) + (float)pathScale * yPos * Math.Cos(rotation * Math.PI / 180)));
  640. DrawStringOnCurrPath(value[i].ToString(), font, pathPoint, fontBaselineHeight, rotation);
  641. }
  642. }
  643. }
  644. // Add the kerning to the next character
  645. if (lastIndividualChar < value.Length)
  646. {
  647. xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X;
  648. }
  649. else
  650. {
  651. xPos += charBounds.Last().Width;
  652. }
  653. }
  654. // Render the string normally
  655. if (lastIndividualChar < value.Length)
  656. {
  657. xPos += (xOffsets.Count > lastIndividualChar ? xOffsets[lastIndividualChar] : 0);
  658. yPos = (yAnchors.Count > lastIndividualChar ? yAnchors[lastIndividualChar] : yPos) +
  659. (yOffsets.Count > lastIndividualChar ? yOffsets[lastIndividualChar] : 0);
  660. DrawStringOnCurrPath(value.Substring(lastIndividualChar), font, new PointF(xPos, yPos),
  661. fontBaselineHeight, rotations.LastOrDefault());
  662. var bounds = font.MeasureString(this.Renderer, value.Substring(lastIndividualChar));
  663. xPos += bounds.Width;
  664. }
  665. NumChars += value.Length;
  666. // Undo any baseline shift. This is not persisted, unlike normal vertical offsets.
  667. this.Current = new PointF(xPos, yPos - baselineShift);
  668. }
  669. }
  670. private void DrawStringOnCurrPath(string value, IFontDefn font, PointF location, float fontBaselineHeight, float rotation)
  671. {
  672. var drawPath = _currPath;
  673. if (rotation != 0.0f) drawPath = new GraphicsPath();
  674. font.AddStringToPath(this.Renderer, drawPath, value, new PointF(location.X, location.Y - fontBaselineHeight));
  675. if (rotation != 0.0f && drawPath.PointCount > 0)
  676. {
  677. using (var matrix = new Matrix())
  678. {
  679. matrix.Translate(-1 * location.X, -1 * location.Y, MatrixOrder.Append);
  680. matrix.Rotate(rotation, MatrixOrder.Append);
  681. matrix.Translate(location.X, location.Y, MatrixOrder.Append);
  682. drawPath.Transform(matrix);
  683. _currPath.AddPath(drawPath, false);
  684. }
  685. }
  686. }
  687. private void EnsurePath()
  688. {
  689. if (_currPath == null)
  690. {
  691. _currPath = new GraphicsPath();
  692. _currPath.StartFigure();
  693. var currState = this;
  694. while (currState != null && currState._xAnchor <= float.MinValue)
  695. {
  696. currState = currState.Parent;
  697. }
  698. currState._anchoredPaths.Add(_currPath);
  699. }
  700. }
  701. private void FlushPath()
  702. {
  703. if (_currPath != null)
  704. {
  705. _currPath.CloseFigure();
  706. // Abort on empty paths (e.g. rendering a space)
  707. if (_currPath.PointCount < 1)
  708. {
  709. _anchoredPaths.Clear();
  710. _xAnchor = float.MinValue;
  711. _currPath = null;
  712. return;
  713. }
  714. if (_xAnchor > float.MinValue)
  715. {
  716. float minX = float.MaxValue;
  717. float maxX = float.MinValue;
  718. RectangleF bounds;
  719. foreach (var path in _anchoredPaths)
  720. {
  721. bounds = path.GetBounds();
  722. if (bounds.Left < minX) minX = bounds.Left;
  723. if (bounds.Right > maxX) maxX = bounds.Right;
  724. }
  725. var xOffset = 0f; //_xAnchor - minX;
  726. switch (Element.TextAnchor)
  727. {
  728. case SvgTextAnchor.Middle:
  729. xOffset -= (maxX - minX) / 2;
  730. break;
  731. case SvgTextAnchor.End:
  732. xOffset -= (maxX - minX);
  733. break;
  734. }
  735. if (xOffset != 0)
  736. {
  737. using (var matrix = new Matrix())
  738. {
  739. matrix.Translate(xOffset, 0);
  740. foreach (var path in _anchoredPaths)
  741. {
  742. path.Transform(matrix);
  743. }
  744. }
  745. }
  746. _anchoredPaths.Clear();
  747. _xAnchor = float.MinValue;
  748. }
  749. if (_finalPath == null)
  750. {
  751. _finalPath = _currPath;
  752. }
  753. else
  754. {
  755. _finalPath.AddPath(_currPath, false);
  756. }
  757. _currPath = null;
  758. }
  759. }
  760. private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<float>> listGetter)
  761. {
  762. var currState = this;
  763. int charCount = 0;
  764. var results = new List<float>();
  765. int resultCount = 0;
  766. while (currState != null)
  767. {
  768. charCount += currState.NumChars;
  769. results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount));
  770. if (results.Count > resultCount)
  771. {
  772. maxCount -= results.Count - resultCount;
  773. charCount += results.Count - resultCount;
  774. resultCount = results.Count;
  775. }
  776. if (maxCount < 1) return results;
  777. currState = currState.Parent;
  778. }
  779. return results;
  780. }
  781. private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<SvgUnit>> listGetter, UnitRenderingType renderingType)
  782. {
  783. var currState = this;
  784. int charCount = 0;
  785. var results = new List<float>();
  786. int resultCount = 0;
  787. while (currState != null)
  788. {
  789. charCount += currState.NumChars;
  790. results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount).Select(p => p.ToDeviceValue(currState.Renderer, renderingType, currState.Element)));
  791. if (results.Count > resultCount)
  792. {
  793. maxCount -= results.Count - resultCount;
  794. charCount += results.Count - resultCount;
  795. resultCount = results.Count;
  796. }
  797. if (maxCount < 1) return results;
  798. currState = currState.Parent;
  799. }
  800. return results;
  801. }
  802. }
  803. /// <summary>Empty text elements are not legal - only write this element if it has children.</summary>
  804. public override bool ShouldWriteElement()
  805. {
  806. return (this.HasChildren() || this.Nodes.Count > 0);
  807. }
  808. }
  809. }
  810. #pragma warning restore