SvgTextBase.cs 36 KB

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