Ruler.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. using PaintDotNet.SystemLayer;
  2. using System;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Drawing.Drawing2D;
  6. using System.Windows.Forms;
  7. namespace PaintDotNet
  8. {
  9. /// <summary>
  10. /// 顶部和左侧的标尺控件
  11. /// </summary>
  12. public sealed class Ruler : UserControl
  13. {
  14. /// <summary>
  15. /// 默认单位,英寸
  16. /// </summary>
  17. private MeasurementUnit measurementUnit = MeasurementUnit.Inch;
  18. public MeasurementUnit MeasurementUnit
  19. {
  20. get
  21. {
  22. return measurementUnit;
  23. }
  24. set
  25. {
  26. if (value != measurementUnit)
  27. {
  28. measurementUnit = value;
  29. Invalidate();
  30. }
  31. }
  32. }
  33. /// <summary>
  34. /// 默认方向 水平
  35. /// </summary>
  36. private Orientation orientation = Orientation.Horizontal;
  37. [DefaultValue(Orientation.Horizontal)]
  38. public Orientation Orientation
  39. {
  40. get
  41. {
  42. return orientation;
  43. }
  44. set
  45. {
  46. if (orientation != value)
  47. {
  48. orientation = value;
  49. Invalidate();
  50. }
  51. }
  52. }
  53. /// <summary>
  54. /// 每英寸长度内的像素点数
  55. /// 默认写死的dpi,96没有实际意义
  56. /// 需要从标尺读取
  57. /// </summary>
  58. private double dpu = 96;
  59. [DefaultValue(96.0)]
  60. public double Dpu
  61. {
  62. get
  63. {
  64. return dpu;
  65. }
  66. set
  67. {
  68. if (value != dpu)
  69. {
  70. dpu = value;
  71. Invalidate();
  72. }
  73. }
  74. }
  75. /// <summary>
  76. /// 封装了缩放功能的结构体
  77. /// </summary>
  78. private ScaleFactor scaleFactor = ScaleFactor.OneToOne;
  79. [Browsable(false)]
  80. public ScaleFactor ScaleFactor
  81. {
  82. get
  83. {
  84. return scaleFactor;
  85. }
  86. set
  87. {
  88. if (scaleFactor != value)
  89. {
  90. scaleFactor = value;
  91. Invalidate();
  92. }
  93. }
  94. }
  95. private float offset = 0;
  96. [DefaultValue(0)]
  97. public float Offset
  98. {
  99. get
  100. {
  101. return offset;
  102. }
  103. set
  104. {
  105. if (offset != value)
  106. {
  107. offset = value;
  108. Invalidate();
  109. }
  110. }
  111. }
  112. private float rulerValue = 0.0f;
  113. [DefaultValue(0)]
  114. public float Value
  115. {
  116. get
  117. {
  118. return rulerValue;
  119. }
  120. set
  121. {
  122. if (this.rulerValue != value)
  123. {
  124. float oldStart = this.scaleFactor.ScaleScalar(this.rulerValue - offset) - 1;
  125. float oldEnd = this.scaleFactor.ScaleScalar(this.rulerValue + 1 - offset) + 1;
  126. RectangleF oldRect;
  127. if (this.orientation == Orientation.Horizontal)
  128. {
  129. oldRect = new RectangleF(oldStart, this.ClientRectangle.Top, oldEnd - oldStart, this.ClientRectangle.Height);
  130. }
  131. else // if (this.orientation == Orientation.Vertical)
  132. {
  133. oldRect = new RectangleF(this.ClientRectangle.Left, oldStart, this.ClientRectangle.Width, oldEnd - oldStart);
  134. }
  135. float newStart = this.scaleFactor.ScaleScalar(value - offset);
  136. float newEnd = this.scaleFactor.ScaleScalar(value + 1 - offset);
  137. RectangleF newRect;
  138. if (this.orientation == Orientation.Horizontal)
  139. {
  140. newRect = new RectangleF(newStart, this.ClientRectangle.Top, newEnd - newStart, this.ClientRectangle.Height);
  141. }
  142. else // if (this.orientation == Orientation.Vertical)
  143. {
  144. newRect = new RectangleF(this.ClientRectangle.Left, newStart, this.ClientRectangle.Width, newEnd - newStart);
  145. }
  146. this.rulerValue = value;
  147. Invalidate(Utility.RoundRectangle(oldRect));
  148. Invalidate(Utility.RoundRectangle(newRect));
  149. }
  150. }
  151. }
  152. private float highlightStart = 0.0f;
  153. public float HighlightStart
  154. {
  155. get
  156. {
  157. return this.highlightStart;
  158. }
  159. set
  160. {
  161. if (this.highlightStart != value)
  162. {
  163. this.highlightStart = value;
  164. Invalidate();
  165. }
  166. }
  167. }
  168. private float highlightLength = 0.0f;
  169. public float HighlightLength
  170. {
  171. get
  172. {
  173. return this.highlightLength;
  174. }
  175. set
  176. {
  177. if (this.highlightLength != value)
  178. {
  179. this.highlightLength = value;
  180. Invalidate();
  181. }
  182. }
  183. }
  184. private bool highlightEnabled = false;
  185. public bool HighlightEnabled
  186. {
  187. get
  188. {
  189. return this.highlightEnabled;
  190. }
  191. set
  192. {
  193. if (this.highlightEnabled != value)
  194. {
  195. this.highlightEnabled = value;
  196. Invalidate();
  197. }
  198. }
  199. }
  200. /// <summary>
  201. /// 构造方法
  202. /// </summary>
  203. public Ruler()
  204. {
  205. SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
  206. }
  207. protected override void OnPaint(PaintEventArgs e)
  208. {
  209. float valueStart = this.scaleFactor.ScaleScalar(this.rulerValue - offset);
  210. float valueEnd = this.scaleFactor.ScaleScalar(this.rulerValue + 1.0f - offset);
  211. float highlightStartPx = this.scaleFactor.ScaleScalar(this.highlightStart - offset);
  212. float highlightEndPx = this.scaleFactor.ScaleScalar(this.highlightStart + this.highlightLength - offset);
  213. RectangleF highlightRect;
  214. RectangleF valueRect;
  215. if (this.orientation == Orientation.Horizontal)
  216. {
  217. valueRect = new RectangleF(valueStart, this.ClientRectangle.Top, valueEnd - valueStart, this.ClientRectangle.Height);
  218. highlightRect = new RectangleF(highlightStartPx, this.ClientRectangle.Top, highlightEndPx - highlightStartPx, this.ClientRectangle.Height);
  219. }
  220. else // if (this.orientation == Orientation.Vertical)
  221. {
  222. valueRect = new RectangleF(this.ClientRectangle.Left, valueStart, this.ClientRectangle.Width, valueEnd - valueStart);
  223. highlightRect = new RectangleF(this.ClientRectangle.Left, highlightStartPx, this.ClientRectangle.Width, highlightEndPx - highlightStartPx);
  224. }
  225. if (!this.highlightEnabled)
  226. {
  227. highlightRect = RectangleF.Empty;
  228. }
  229. if (this.orientation == Orientation.Horizontal)
  230. {
  231. e.Graphics.DrawLine(
  232. SystemPens.WindowText,
  233. UI.ScaleWidth(15),
  234. ClientRectangle.Top,
  235. UI.ScaleWidth(15),
  236. ClientRectangle.Bottom);
  237. string abbStringName = "MeasurementUnit." + this.MeasurementUnit.ToString() + ".Abbreviation";
  238. string abbString = PdnResources.GetString(abbStringName);
  239. //e.Graphics.DrawString(abbString, Font, SystemBrushes.WindowText, UI.ScaleWidth(-2), 0);
  240. }
  241. Region clipRegion = new Region(highlightRect);
  242. clipRegion.Xor(valueRect);
  243. if (this.orientation == Orientation.Horizontal)
  244. {
  245. clipRegion.Exclude(new Rectangle(0, 0, UI.ScaleWidth(16), ClientRectangle.Height));
  246. }
  247. e.Graphics.SetClip(clipRegion, CombineMode.Replace);
  248. DrawRuler(e, true);
  249. clipRegion.Xor(this.ClientRectangle);
  250. if (this.orientation == Orientation.Horizontal)
  251. {
  252. clipRegion.Exclude(new Rectangle(0, 0, UI.ScaleWidth(16), ClientRectangle.Height - 1));
  253. }
  254. e.Graphics.SetClip(clipRegion, CombineMode.Replace);
  255. DrawRuler(e, false);
  256. clipRegion.Dispose();
  257. }
  258. private static readonly float[] majorDivisors =
  259. new float[]
  260. {
  261. 10.0f,
  262. 10.0f,
  263. 10.0f
  264. };
  265. private int[] GetSubdivs(MeasurementUnit unit)
  266. {
  267. switch (unit)
  268. {
  269. case MeasurementUnit.Inch:
  270. {
  271. return new int[] { 2 };
  272. }
  273. case MeasurementUnit.Mil:
  274. {
  275. return new int[] { 2 };
  276. }
  277. case MeasurementUnit.Centimeter:
  278. {
  279. return new int[] { 2 };
  280. }
  281. case MeasurementUnit.Millimeter:
  282. {
  283. return new int[] { 2 };
  284. }
  285. case MeasurementUnit.Micron:
  286. {
  287. return new int[] { 2 };
  288. }
  289. case MeasurementUnit.Nano:
  290. {
  291. return new int[] { 2 };
  292. }
  293. default:
  294. {
  295. return null;
  296. }
  297. }
  298. }
  299. /// <summary>
  300. /// 分割x轴
  301. /// </summary>
  302. /// <param name="g"></param>
  303. /// <param name="pen"></param>
  304. /// <param name="x"></param>
  305. /// <param name="delta"></param>
  306. /// <param name="index"></param>
  307. /// <param name="y"></param>
  308. /// <param name="height"></param>
  309. /// <param name="subdivs"></param>
  310. private void SubdivideX(
  311. Graphics g,
  312. Pen pen,
  313. float x,
  314. float delta,
  315. int index,
  316. float y,
  317. float height,
  318. int[] subdivs)
  319. {
  320. g.DrawLine(pen, x, y, x, y + height);
  321. if (index > 10)
  322. {
  323. return;
  324. }
  325. float div;
  326. if (subdivs != null && index >= 0)
  327. {
  328. div = subdivs[index % subdivs.Length];
  329. }
  330. else if (index < 0)
  331. {
  332. div = majorDivisors[(-index - 1) % majorDivisors.Length];
  333. }
  334. else
  335. {
  336. return;
  337. }
  338. for (int i = 0; i < div; i++)
  339. {
  340. if ((delta / div) > 3.5)
  341. {
  342. SubdivideX(g, pen, x + delta * i / div, delta / div, index + 1, y, height / div + 0.5f, subdivs);
  343. }
  344. }
  345. }
  346. /// <summary>
  347. /// 分割y轴
  348. /// </summary>
  349. /// <param name="g"></param>
  350. /// <param name="pen"></param>
  351. /// <param name="y"></param>
  352. /// <param name="delta"></param>
  353. /// <param name="index"></param>
  354. /// <param name="x"></param>
  355. /// <param name="width"></param>
  356. /// <param name="subdivs"></param>
  357. private void SubdivideY(
  358. Graphics g,
  359. Pen pen,
  360. float y,
  361. float delta,
  362. int index,
  363. float x,
  364. float width,
  365. int[] subdivs)
  366. {
  367. g.DrawLine(pen, x, y, x + width, y);
  368. if (index > 10)
  369. {
  370. return;
  371. }
  372. float div;
  373. if (subdivs != null && index >= 0)
  374. {
  375. div = subdivs[index % subdivs.Length];
  376. }
  377. else if (index < 0)
  378. {
  379. div = majorDivisors[(-index - 1) % majorDivisors.Length];
  380. }
  381. else
  382. {
  383. return;
  384. }
  385. for (int i = 0; i < div; i++)
  386. {
  387. if ((delta / div) > 3.5)
  388. {
  389. SubdivideY(g, pen, y + delta * i / div, delta / div, index + 1, x, width / div + 0.5f, subdivs);
  390. }
  391. }
  392. }
  393. /// <summary>
  394. /// 绘制标尺
  395. /// </summary>
  396. /// <param name="e"></param>
  397. /// <param name="highlighted"></param>
  398. private void DrawRuler(PaintEventArgs e, bool highlighted)
  399. {
  400. Pen pen;
  401. Brush cursorBrush;
  402. Brush textBrush;
  403. StringFormat textFormat = new StringFormat();
  404. int maxPixel;
  405. Color cursorColor;
  406. /**
  407. if (highlighted)
  408. {
  409. e.Graphics.Clear(SystemColors.Highlight);
  410. pen = SystemPens.HighlightText;
  411. textBrush = SystemBrushes.HighlightText;
  412. cursorColor = SystemColors.Window;
  413. }
  414. else
  415. {
  416. e.Graphics.Clear(SystemColors.Window);
  417. pen = SystemPens.WindowText;
  418. textBrush = SystemBrushes.WindowText;
  419. cursorColor = SystemColors.Highlight;
  420. }**/
  421. pen = SystemPens.WindowText;
  422. textBrush = SystemBrushes.WindowText;
  423. cursorColor = SystemColors.Highlight;
  424. cursorColor = Color.FromArgb(128, cursorColor);
  425. cursorBrush = new SolidBrush(cursorColor);
  426. if (orientation == Orientation.Horizontal)
  427. {
  428. maxPixel = ScaleFactor.UnscaleScalar(ClientRectangle.Width);
  429. textFormat.Alignment = StringAlignment.Near;
  430. textFormat.LineAlignment = StringAlignment.Far;
  431. }
  432. else // if (orientation == Orientation.Vertical)
  433. {
  434. maxPixel = ScaleFactor.UnscaleScalar(ClientRectangle.Height);
  435. textFormat.Alignment = StringAlignment.Near;
  436. textFormat.LineAlignment = StringAlignment.Near;
  437. textFormat.FormatFlags |= StringFormatFlags.DirectionVertical;
  438. }
  439. float majorSkip = 1f;
  440. int majorSkipPower = 0;
  441. float majorDivisionLength = (float)dpu;
  442. float majorDivisionPixels = (float)ScaleFactor.ScaleScalar(majorDivisionLength);
  443. int[] subdivs = GetSubdivs(measurementUnit);
  444. float offsetPixels = ScaleFactor.ScaleScalar((float)offset);
  445. int startMajor = (int)(offset / majorDivisionLength) - 1;
  446. int endMajor = (int)((offset + maxPixel) / majorDivisionLength) + 1;
  447. if (orientation == Orientation.Horizontal)
  448. {
  449. // draw Value
  450. if (!highlighted)
  451. {
  452. PointF pt = scaleFactor.ScalePointJustX(new PointF(ClientRectangle.Left + Value - Offset, ClientRectangle.Top));
  453. SizeF size = new SizeF(Math.Max(1, scaleFactor.ScaleScalar(1.0f)), ClientRectangle.Height);
  454. pt.X -= 0.5f;
  455. CompositingMode oldCM = e.Graphics.CompositingMode;
  456. e.Graphics.CompositingMode = CompositingMode.SourceOver;
  457. e.Graphics.FillRectangle(cursorBrush, new RectangleF(pt, size));
  458. e.Graphics.CompositingMode = oldCM;
  459. }
  460. // draw border
  461. e.Graphics.DrawLine(SystemPens.WindowText, new Point(ClientRectangle.Left, ClientRectangle.Bottom - 1),
  462. new Point(ClientRectangle.Right - 1, ClientRectangle.Bottom - 1));
  463. }
  464. else if (orientation == Orientation.Vertical)
  465. {
  466. // draw Value
  467. if (!highlighted)
  468. {
  469. PointF pt = scaleFactor.ScalePointJustY(new PointF(ClientRectangle.Left, ClientRectangle.Top + Value - Offset));
  470. SizeF size = new SizeF(ClientRectangle.Width, Math.Max(1, scaleFactor.ScaleScalar(1.0f)));
  471. pt.Y -= 0.5f;
  472. CompositingMode oldCM = e.Graphics.CompositingMode;
  473. e.Graphics.CompositingMode = CompositingMode.SourceOver;
  474. e.Graphics.FillRectangle(cursorBrush, new RectangleF(pt, size));
  475. e.Graphics.CompositingMode = oldCM;
  476. }
  477. // draw border
  478. e.Graphics.DrawLine(SystemPens.WindowText, new Point(ClientRectangle.Right - 1, ClientRectangle.Top),
  479. new Point(ClientRectangle.Right - 1, ClientRectangle.Bottom - 1));
  480. }
  481. while (majorDivisionPixels * majorSkip < 60)
  482. {
  483. majorSkip *= majorDivisors[majorSkipPower % majorDivisors.Length];
  484. ++majorSkipPower;
  485. }
  486. startMajor = (int)Math.Round((majorSkip * Math.Floor(startMajor / (double)majorSkip)));
  487. //decimal skip = Math.Round(Convert.ToDecimal(majorSkip), 1, MidpointRounding.AwayFromZero);
  488. //if(skip == 0)
  489. //{
  490. // skip = Math.Round(Convert.ToDecimal(majorSkip), 4, MidpointRounding.AwayFromZero);
  491. //}
  492. for (float major = startMajor; major <= endMajor; major += majorSkip)//skip
  493. {
  494. float majorMarkPos = ((float)major * majorDivisionPixels) - offsetPixels;
  495. //string majorText = major.ToString();//(Math.Round(Convert.ToDecimal(major), 4, MidpointRounding.AwayFromZero)).ToString();
  496. if (orientation == Orientation.Horizontal)
  497. {
  498. SubdivideX(e.Graphics, pen, ClientRectangle.Left + majorMarkPos, majorDivisionPixels * majorSkip, -majorSkipPower, ClientRectangle.Top, ClientRectangle.Height, subdivs);
  499. //e.Graphics.DrawString(majorText, Font, textBrush, new PointF(ClientRectangle.Left + majorMarkPos, ClientRectangle.Bottom), textFormat);
  500. }
  501. else // if (orientation == Orientation.Vertical)
  502. {
  503. SubdivideY(e.Graphics, pen, ClientRectangle.Top + majorMarkPos, majorDivisionPixels * majorSkip, -majorSkipPower, ClientRectangle.Left, ClientRectangle.Width, subdivs);
  504. // e.Graphics.DrawString(majorText, Font, textBrush, new PointF(ClientRectangle.Left, ClientRectangle.Top + majorMarkPos), textFormat);
  505. }
  506. }
  507. //
  508. // 以下是绘制文字
  509. //
  510. decimal skip = this.SkipNumberCalc(measurementUnit);
  511. int k = 0;
  512. int f = this.SkipInterval(measurementUnit);
  513. for (decimal major = startMajor; major <= endMajor; major += skip)//majorSkip
  514. {
  515. float majorMarkPos = ((float)major * majorDivisionPixels) - offsetPixels;
  516. string majorText = major.ToString();//(Math.Round(Convert.ToDecimal(major), 4, MidpointRounding.AwayFromZero)).ToString();
  517. if (orientation == Orientation.Horizontal)
  518. {
  519. if(k % f == 0)
  520. {
  521. e.Graphics.DrawString(majorText, Font, textBrush, new PointF(ClientRectangle.Left + majorMarkPos, ClientRectangle.Bottom), textFormat);
  522. }
  523. }
  524. else // if (orientation == Orientation.Vertical)
  525. {
  526. if (k % f == 0)
  527. {
  528. e.Graphics.DrawString(majorText, Font, textBrush, new PointF(ClientRectangle.Left, ClientRectangle.Top + majorMarkPos), textFormat);
  529. }
  530. }
  531. k++;
  532. }
  533. textFormat.Dispose();
  534. }
  535. /// <summary>
  536. /// 根据不同单位返回不同的间隔长度
  537. /// </summary>
  538. /// <param name="unit"></param>
  539. /// <returns></returns>
  540. private decimal SkipNumberCalc(MeasurementUnit unit)
  541. {
  542. decimal skip = 100M;
  543. if (unit == MeasurementUnit.Inch)
  544. {
  545. skip = 0.001M;
  546. }
  547. else if (unit == MeasurementUnit.Mil)
  548. {
  549. skip = 0.1M;//###Mil
  550. }
  551. else if (unit == MeasurementUnit.Centimeter)
  552. {
  553. skip = 0.01M;
  554. }
  555. else if (unit == MeasurementUnit.Millimeter)
  556. {
  557. skip = 0.1M;
  558. }
  559. else if (unit == MeasurementUnit.Micron)
  560. {
  561. skip = 10M;
  562. }
  563. else if (unit == MeasurementUnit.Nano)
  564. {
  565. skip = 10000M;
  566. }
  567. return skip;
  568. }
  569. /// <summary>
  570. /// 不同的缩放比列下,间隔多少单元格绘制文字
  571. /// </summary>
  572. /// <param name="unit"></param>
  573. /// <returns></returns>
  574. private int SkipInterval(MeasurementUnit unit)
  575. {
  576. int f = 1;
  577. if (scaleFactor.Ratio <= 0.05f)
  578. {
  579. switch (unit)
  580. {
  581. case MeasurementUnit.Pixel:
  582. case MeasurementUnit.Centimeter:
  583. case MeasurementUnit.Millimeter:
  584. f = 100;
  585. break;
  586. case MeasurementUnit.Inch:
  587. case MeasurementUnit.Mil://###Mil
  588. case MeasurementUnit.Micron:
  589. case MeasurementUnit.Nano:
  590. f = 1000;
  591. break;
  592. }
  593. }
  594. else if (scaleFactor.Ratio <= 0.1f)
  595. {
  596. switch (unit)
  597. {
  598. case MeasurementUnit.Pixel:
  599. case MeasurementUnit.Centimeter:
  600. case MeasurementUnit.Millimeter:
  601. f = 10;
  602. break;
  603. case MeasurementUnit.Inch:
  604. case MeasurementUnit.Mil://###Mil
  605. case MeasurementUnit.Micron:
  606. case MeasurementUnit.Nano:
  607. f = 100;
  608. break;
  609. }
  610. }
  611. else if (scaleFactor.Ratio <= 0.5f)
  612. {
  613. switch (unit)
  614. {
  615. case MeasurementUnit.Pixel:
  616. case MeasurementUnit.Centimeter:
  617. case MeasurementUnit.Millimeter:
  618. f = 5;
  619. break;
  620. case MeasurementUnit.Inch:
  621. case MeasurementUnit.Mil://###Mil
  622. case MeasurementUnit.Micron:
  623. case MeasurementUnit.Nano:
  624. f = 50;
  625. break;
  626. }
  627. }
  628. else
  629. {
  630. switch (unit)
  631. {
  632. case MeasurementUnit.Pixel:
  633. case MeasurementUnit.Centimeter:
  634. case MeasurementUnit.Millimeter:
  635. f = 1;
  636. break;
  637. case MeasurementUnit.Inch:
  638. case MeasurementUnit.Mil://###Mil
  639. case MeasurementUnit.Micron:
  640. case MeasurementUnit.Nano:
  641. f = 10;
  642. break;
  643. }
  644. }
  645. return f;
  646. }
  647. }
  648. }