Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3531 lines
101 KiB

  1. /******************************Module*Header*******************************\
  2. * Module Name: pathwide.cxx
  3. *
  4. * This module generates the outlines for geometric wide-lines. Given
  5. * a path for a line, it generates a new path that, when filled using
  6. * a winding mode fill, yields the resulting widened line.
  7. *
  8. * Geometric wide-lines may have different end-cap and line-join
  9. * attributes. They may also be styled.
  10. *
  11. * THINGS TO NOTE
  12. *
  13. * The path is already in device coordinates, so the resulting wide-line
  14. * is also calculated in device coordinates.
  15. *
  16. * The key thing to note is that the pen's shape is defined in world space, so
  17. * its shape in device space depends on the current world-to-device
  18. * transform (which may be an arbitrary 2 by 2 affine transform). Pens
  19. * are circular in world space, with their diameter there defined by the
  20. * pen width. However, in device space a pen shape is only guaranteed to be
  21. * an ellipse symmetrical about some arbitrary axis. And vectors that are
  22. * perpendicular in world space are not necessarily perpendicular in device
  23. * space (which is important for calculating flat and square end-caps, and
  24. * bevel joins).
  25. *
  26. * HOW WE DO IT
  27. *
  28. * We implement wide-lines by first generating in device space a convex
  29. * polygonal approximation to the pen's shape. We 'drag' this polygon
  30. * along the given path; the outline of a widened line is generated by
  31. * the point on the pen polygon that is furthest from the line's ray
  32. * (called the 'draw' vertex). This point may be found by a simple
  33. * binary search using entirely integer arithmetic.
  34. *
  35. * This approach makes round round joins and round caps very efficient:
  36. * the points for the resulting round part is simply the points between
  37. * draw vertices.
  38. *
  39. * To compute true perpendiculars (for flat and square end-caps, and
  40. * bevel joins) we interpolate between vertices on the pen polygon
  41. * (once again using only integer math).
  42. *
  43. * The lengths of the dashes and gaps for styled lines are defined in
  44. * world space; to do styling we computed every line segment's length
  45. * in world space, and divide it accordingly.
  46. *
  47. * HOBBY WIDE-LINES
  48. *
  49. * Rendering the wide-lines using a polygonized pen allows us to take
  50. * advantage of Hobby wide-lines. Hobby's wide-line algorithm
  51. * generates 'optimally shaped' pens that will yield lines of constant-
  52. * looking width, independent of the direction of the lines. (OS/2's
  53. * PM suffers from having its thin wide-lines appear not to be of
  54. * constant width).
  55. *
  56. * Unfortunately, Hobby's algorithm is fairly expensive to generate an
  57. * optimal pen shape polygon given its width and the current transform;
  58. * it is significantly faster for us to generate a quick-and-dirty
  59. * polygon approximation by creating the ellipse out of Bezier curves and
  60. * flattening it.
  61. *
  62. * But Hobby pens are most important for small, near circular pens,
  63. * where the eye can readily detect imperfections in the line. So we
  64. * support Hobby wide-lines only for small, near circular pens, and we
  65. * keep these pen shapes in a pre-computed table.
  66. *
  67. * See:
  68. *
  69. * John Hobby, "Digitized Brush Trajectories", PhD Dissertation,
  70. * Stanford University, August 1985
  71. *
  72. * GENERATING BOTH SIDES AT THE SAME TIME
  73. *
  74. * We also generate both sides of the wide-line outline as we traverse
  75. * the path, keeping track of both sides in separate paths (the outlines
  76. * of the 'left' and 'right' sides of the wide-line). When the end of a
  77. * figure (that is, the end of a sub-path) is reached, we reverse all the
  78. * points in the 'left' path and append it to the 'right', and close the
  79. * result.
  80. *
  81. * This could have been implemented by traversing the path once to generate
  82. * one side, then traversing it again in the reverse direction to generate
  83. * the other side. However, most of the hard computations for perpendiculars
  84. * and such would have to be done twice. Our method is faster, although
  85. * there is more book-keeping.
  86. *
  87. * BEZIERS
  88. *
  89. * We really only know how to widen lines, so we convert Beziers as we
  90. * read them to their polyline approximations. We 'flatten' Beziers to
  91. * constant error, independent of pen width, and make sure we apply 'round'
  92. * joins to the interior polylines comprising the Bezier. The round join
  93. * handles extreme cases such as cusps, so there really is no need to
  94. * flatten to less error for larger pens.
  95. *
  96. * (This is in contrast to OS/2's PM which pre-flattens its curves prior
  97. * to widening, and applies the regular joins to the interior of the curve.
  98. * For wider pens, the allowed error in the flattening must be decreased
  99. * because sharp curves would not look 'round' at cusp-points when using
  100. * bevel or miter joins.)
  101. *
  102. * We flatten Bezier curves as we traverse path (instead of only widening
  103. * paths that are already flattened) because it is useful to know the
  104. * Bezier data. We use the control points to calculate true perpendiculars
  105. * at the end-points of curves, and we have to know when we're in the
  106. * interior of the Bezier to always apply round joins.
  107. *
  108. * Advantages: o We're much faster
  109. *
  110. * o Produce better looking curves
  111. *
  112. * Disadvantages: o More book-keeping
  113. *
  114. * o Have to be careful when mixing perpendiculars (such
  115. * as from miter joins or flat caps) with round joins
  116. *
  117. * o Perpendiculars on styled curves may not be as accurate
  118. * for wide-lines -- we use the approximating lines to
  119. * calculate the perp, and more lines would give better
  120. * perps.
  121. *
  122. * However, it can also be argued that due to the
  123. * snapping effect of the device-space grid, approximating
  124. * the curve into more small lines would actually make
  125. * the perps _less_ accurate.
  126. *
  127. * LAZY EVALUATION
  128. *
  129. * Computing draw vectors and perpendiculars are very expensive operations,
  130. * so we lazily evaluate them -- we compute them only when we need them.
  131. * With styling, it may happen we completely skip over some lines (so we
  132. * never do any evaluation), or that we break a line into many small pieces,
  133. * and so re-use perps and draw-vectors (saving us time and ensuring that
  134. * style ends are always perpendicular for styled segments).
  135. *
  136. * Created: 23-Sep-1991
  137. * Author: J. Andrew Goossen [andrewgo]
  138. *
  139. * Copyright (c) 1991-1999 Microsoft Corporation
  140. *
  141. \**************************************************************************/
  142. #include "precomp.hxx"
  143. #include "flhack.hxx"
  144. #include "pathwide.hxx"
  145. #ifdef DEBUG_WIDE
  146. #define INLINE
  147. #else
  148. #define INLINE inline
  149. #endif
  150. #ifdef DEBUG_WIDE
  151. LONG lConv(EFLOAT ef);
  152. #endif
  153. POINTFIX WIDEPENOBJ::aptfxHobby1[] = {
  154. {0, 8}, {8, 0}, {0, -8}, {-8, 0}
  155. };
  156. POINTFIX WIDEPENOBJ::aptfxHobby2[] = {
  157. {8, 16}, {16, 0}, {8, -16}, {-8, -16},
  158. {-16, 0}
  159. };
  160. POINTFIX WIDEPENOBJ::aptfxHobby3[] = {
  161. {24, 8}, {24, -8}, {8, -24}, {-8, -24},
  162. {-24, -8}, {-24, 8}
  163. };
  164. POINTFIX WIDEPENOBJ::aptfxHobby4[] = {
  165. {32, 8}, {32, -8}, {24, -24}, {8, -32},
  166. {-8, -32}, {-24, -24}, {-32, -8}, {-32, 8}
  167. };
  168. POINTFIX WIDEPENOBJ::aptfxHobby5[] = {
  169. {40, 8}, {40, -8}, {32, -24}, {24, -32},
  170. {8, -40}, {-8, -40}, {-24, -32}, {-32, -24},
  171. {-40, -8}, {-40, 8}
  172. };
  173. POINTFIX WIDEPENOBJ::aptfxHobby6[] = {
  174. {48, 8}, {48, -8}, {40, -24}, {24, -40},
  175. {8, -48}, {-8, -48}, {-24, -40}, {-40, -24},
  176. {-48, -8}, {-48, 8}
  177. };
  178. HOBBY WIDEPENOBJ::ahob[HOBBY_TABLE_SIZE] = {
  179. { aptfxHobby1, sizeof(aptfxHobby1) / sizeof(POINTFIX) },
  180. { aptfxHobby2, sizeof(aptfxHobby2) / sizeof(POINTFIX) },
  181. { aptfxHobby3, sizeof(aptfxHobby3) / sizeof(POINTFIX) },
  182. { aptfxHobby4, sizeof(aptfxHobby4) / sizeof(POINTFIX) },
  183. { aptfxHobby5, sizeof(aptfxHobby5) / sizeof(POINTFIX) },
  184. { aptfxHobby6, sizeof(aptfxHobby6) / sizeof(POINTFIX) }
  185. };
  186. /******************************Public*Routine******************************\
  187. * bTurnLeftRandom(pa, pb)
  188. *
  189. * Returns TRUE if the sign of the cross product of a X b is positive.
  190. * For simple minds like mine, it returns:
  191. *
  192. * TRUE if b 'turns left' from a (cross product is < 0)
  193. * FALSE if b is parallel to or 'turns right' from a (cross is >= 0)
  194. *
  195. * When the vectors are random, half the time we take a short-cut by
  196. * checking the signs. Remember that we're in device space, where
  197. * positive 'y' is down!
  198. *
  199. * History:
  200. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  201. * Wrote it.
  202. \**************************************************************************/
  203. INLINE BOOL bTurnLeftRandom(
  204. PEVECTORFX pa,
  205. PEVECTORFX pb)
  206. {
  207. BOOL bRet;
  208. // If the signs are correct, we don't even have to multiply:
  209. if ((pa->x ^ pa->y ^ pb->x ^ pb->y) < 0)
  210. bRet = ((pa->x ^ pb->y) < 0);
  211. else
  212. {
  213. LONGLONG ad;
  214. LONGLONG bc;
  215. // Check sign of (pa->x * pb->y - pa->y * pb->x) = (ad - bc)
  216. ad = Int32x32To64(pa->x, pb->y);
  217. bc = Int32x32To64(pa->y, pb->x);
  218. bRet = (ad < bc);
  219. }
  220. return(bRet);
  221. }
  222. /******************************Public*Routine******************************\
  223. * WIDEPATHOBJ::bGrowPath()
  224. *
  225. * Creates a new path record. Adds to the end of the path, and sets
  226. * 'pptfxPathRecCurrent' and 'pptfxPathRecEnd'.
  227. *
  228. * History:
  229. * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  230. * Stole it from PaulB's pprFlattenRec.
  231. \**************************************************************************/
  232. BOOL WIDEPATHOBJ::bGrowPath()
  233. {
  234. PPATHALLOC ppa = ppath->ppachain;
  235. COUNT cMax = 0;
  236. if (ppa != (PPATHALLOC) NULL)
  237. {
  238. // We have a current pathalloc, see how much will fit
  239. // computation done into temps to avoid compiler assertion!
  240. PPOINTFIX pptfxStart = &(ppa->pprfreestart->aptfx[0]);
  241. PPOINTFIX pptfxEnd = (PPOINTFIX) ((char *)ppa + ppa->siztPathAlloc);
  242. if (pptfxEnd > pptfxStart)
  243. {
  244. //Sundown truncation
  245. ASSERT4GB((ULONGLONG)(pptfxEnd - pptfxStart));
  246. cMax = (COUNT)(pptfxEnd - pptfxStart);
  247. }
  248. }
  249. // Now we can decide if we need a new pathalloc
  250. if (cMax < PATHALLOCTHRESHOLD)
  251. {
  252. // allocate a new pathalloc, link it into path
  253. if ( (ppa = newpathalloc()) == (PPATHALLOC) NULL)
  254. return(FALSE);
  255. ppa->ppanext = ppath->ppachain;
  256. ppath->ppachain = ppa;
  257. // adjust maxadd
  258. // Sundown truncation
  259. ASSERT4GB((ULONGLONG)(((char *)ppa + ppa->siztPathAlloc) -
  260. (char *)ppa->pprfreestart));
  261. ULONGSIZE_T numbytes = (ULONG)(((char *)ppa + ppa->siztPathAlloc) -
  262. (char *)ppa->pprfreestart);
  263. cMax = (numbytes - offsetof(PATHRECORD, aptfx))/sizeof(POINTFIX);
  264. }
  265. // Add pathrecord to end of path
  266. PPATHREC pprNew = ppa->pprfreestart;
  267. if (ppath->pprlast == (PPATHREC) NULL)
  268. {
  269. ppath->pprfirst = pprNew;
  270. pprNew->pprprev = (PPATHREC) NULL;
  271. }
  272. else
  273. {
  274. ppath->pprlast->pprnext = pprNew;
  275. pprNew->pprprev = ppath->pprlast;
  276. }
  277. ppath->pprlast = pprNew;
  278. pprNew->pprnext = (PPATHREC) NULL;
  279. pprNew->count = 0;
  280. // pptfxPathRecCurrent points to first available spot. pptfxPathRecEnd
  281. // points to first spot *after* the last available spot.
  282. pptfxPathRecCurrent = &pprNew->aptfx[0];
  283. pptfxPathRecEnd = &pprNew->aptfx[cMax];
  284. return(TRUE);
  285. }
  286. /******************************Public*Routine******************************\
  287. * WIDEPATHOBJ::vGrowPathAndAddPoint(pptfx)
  288. *
  289. * Adds to the current path by creating a new record and adding the
  290. * specified point. If this fails, the path is marked as being out-of-
  291. * memory.
  292. *
  293. * History:
  294. * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  295. * Wrote it.
  296. \**************************************************************************/
  297. VOID WIDEPATHOBJ::vGrowPathAndAddPoint(
  298. PPOINTFIX pptfx,
  299. PEVECTORFX pvec,
  300. BOOL bInvert)
  301. {
  302. ASSERT4GB((LONGLONG)(pptfxPathRecCurrent - ppath->pprlast->aptfx));
  303. COUNT cpt = (COUNT)(pptfxPathRecCurrent - ppath->pprlast->aptfx);
  304. ppath->pprlast->count = cpt;
  305. ppath->ppachain->pprfreestart = NEXTPATHREC(ppath->pprlast);
  306. if (bValid())
  307. {
  308. if (!bGrowPath())
  309. vSetError();
  310. else
  311. {
  312. ppath->pprlast->flags = 0;
  313. *pptfxPathRecCurrent = *pptfx;
  314. if (pvec != (PEVECTORFX) NULL)
  315. {
  316. if (bInvert)
  317. {
  318. pptfxPathRecCurrent->x -= pvec->x;
  319. pptfxPathRecCurrent->y -= pvec->y;
  320. }
  321. else
  322. {
  323. pptfxPathRecCurrent->x += pvec->x;
  324. pptfxPathRecCurrent->y += pvec->y;
  325. }
  326. }
  327. pptfxPathRecCurrent++;
  328. }
  329. }
  330. }
  331. /******************************Public*Routine******************************\
  332. * WIDEPATHOBJ::bBeginFigure()
  333. *
  334. * Creates the start of a new figure (sub-path) in the path. Must be
  335. * called before vAddPoint().
  336. *
  337. * History:
  338. * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  339. * Wrote it.
  340. \**************************************************************************/
  341. BOOL WIDEPATHOBJ::bBeginFigure()
  342. {
  343. if (bValid())
  344. {
  345. #ifdef DEBUG_WIDE
  346. ASSERTGDI(!bOpenPath, "BeginFigure on already open path!");
  347. bOpenPath = TRUE;
  348. #endif
  349. if (bGrowPath())
  350. {
  351. ppath->pprlast->flags = PD_BEGINSUBPATH;
  352. return(TRUE);
  353. }
  354. vSetError();
  355. }
  356. return(FALSE);
  357. }
  358. /******************************Public*Routine******************************\
  359. * WIDEPATHOBJ::vEndFigure()
  360. *
  361. * Ends the open figure. Must be called after all vAddPoint()'s are done
  362. * for the figure.
  363. *
  364. * History:
  365. * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  366. * Wrote it.
  367. \**************************************************************************/
  368. VOID WIDEPATHOBJ::vEndFigure()
  369. {
  370. #ifdef DEBUG_WIDE
  371. ASSERTGDI(bOpenPath, "vEndFigure on unopen path!");
  372. bOpenPath = FALSE;
  373. #endif
  374. //Sundown truncation
  375. ASSERT4GB((ULONGLONG)(pptfxPathRecCurrent - ppath->pprlast->aptfx));
  376. COUNT cpt = (COUNT)(pptfxPathRecCurrent - ppath->pprlast->aptfx);
  377. ppath->pprlast->flags |= PD_ENDSUBPATH;
  378. ppath->pprlast->count = cpt;
  379. // Adjust the pathalloc record:
  380. ppath->ppachain->pprfreestart = NEXTPATHREC(ppath->pprlast);
  381. }
  382. /******************************Public*Routine******************************\
  383. * WIDEPATHOBJ::vPrependBeforeFigure()
  384. *
  385. * Moves the last figure in path and prepends to the first figure.
  386. *
  387. * History:
  388. * 24-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  389. * Wrote it.
  390. \**************************************************************************/
  391. INLINE VOID WIDEPATHOBJ::vPrependBeforeFigure()
  392. {
  393. #ifdef DEBUG_WIDE
  394. ASSERTGDI(!bOpenPath, "vPrependBeforeFigure on open path!");
  395. #endif
  396. PPATHREC pprSrcStart;
  397. PPATHREC pprTargStart;
  398. // Find the start of the last figure:
  399. pprSrcStart = ppath->pprlast;
  400. while (!(pprSrcStart->flags & PD_BEGINSUBPATH))
  401. {
  402. pprSrcStart = pprSrcStart->pprprev;
  403. ASSERTGDI(pprSrcStart != (PPATHREC) NULL, "Couldn't find start");
  404. }
  405. // Find the start of the 2nd last figure:
  406. pprTargStart = pprFigureStart;
  407. ASSERTGDI(pprTargStart != (PPATHREC) NULL &&
  408. (pprTargStart->flags & PD_BEGINSUBPATH), "Funky pprFigureStart");
  409. PPATHREC pprTargEnd = pprSrcStart->pprprev;
  410. PPATHREC pprSrcEnd = ppath->pprlast;
  411. // 'targ' is currently before 'src', and 'src' is the last figure in the
  412. // path. We'll move 'src' to be before 'targ':
  413. ppath->pprlast = pprTargEnd;
  414. // Hmmm, seems to me we're guaranteed to have pprprev NULL here because
  415. // we're looking at the start of the path:
  416. if (pprTargStart->pprprev == (PPATHREC) NULL)
  417. ppath->pprfirst = pprSrcStart;
  418. else
  419. pprTargStart->pprprev->pprnext = pprSrcStart;
  420. pprSrcStart->pprprev = pprTargStart->pprprev;
  421. pprSrcEnd->pprnext = pprTargStart;
  422. pprTargStart->pprprev = pprSrcEnd;
  423. pprTargEnd->pprnext = (PPATHREC) NULL;
  424. // We're prepending this to the start of the first figure, so clean up
  425. // the flags:
  426. pprTargStart->flags &= ~PD_BEGINSUBPATH;
  427. pprSrcEnd->flags &= ~(PD_ENDSUBPATH | PD_CLOSEFIGURE);
  428. }
  429. /******************************Public*Routine******************************\
  430. * WIDEPATHOBJ::vPrependBeforeSubpath()
  431. *
  432. * Moves the last figure in path and prepends it to the previous subpath.
  433. *
  434. * History:
  435. * 24-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  436. * Wrote it.
  437. \**************************************************************************/
  438. INLINE VOID WIDEPATHOBJ::vPrependBeforeSubpath()
  439. {
  440. #ifdef DEBUG_WIDE
  441. ASSERTGDI(!bOpenPath, "vPrependBeforeSubpath on open path!");
  442. #endif
  443. PPATHREC pprSrcStart;
  444. PPATHREC pprTargStart;
  445. // Find the start of the last figure:
  446. pprSrcStart = ppath->pprlast;
  447. while (!(pprSrcStart->flags & PD_BEGINSUBPATH))
  448. {
  449. pprSrcStart = pprSrcStart->pprprev;
  450. ASSERTGDI(pprSrcStart != (PPATHREC) NULL, "Couldn't find start");
  451. }
  452. // Find the start of the 2nd last figure:
  453. ASSERTGDI(pprSrcStart->pprprev != (PPATHREC) NULL, "Was no previous figure");
  454. pprTargStart = pprSrcStart->pprprev;
  455. while (!(pprTargStart->flags & PD_BEGINSUBPATH))
  456. {
  457. pprTargStart = pprTargStart->pprprev;
  458. ASSERTGDI(pprTargStart != (PPATHREC) NULL, "Couldn't find previous figure");
  459. }
  460. PPATHREC pprTargEnd = pprSrcStart->pprprev;
  461. PPATHREC pprSrcEnd = ppath->pprlast;
  462. // 'targ' is currently before 'src', and 'src' is the last figure in the
  463. // path. We'll move 'src' to be before 'targ':
  464. ppath->pprlast = pprTargEnd;
  465. if (pprTargStart->pprprev == (PPATHREC) NULL)
  466. ppath->pprfirst = pprSrcStart;
  467. else
  468. pprTargStart->pprprev->pprnext = pprSrcStart;
  469. pprSrcStart->pprprev = pprTargStart->pprprev;
  470. pprSrcEnd->pprnext = pprTargStart;
  471. pprTargStart->pprprev = pprSrcEnd;
  472. pprTargEnd->pprnext = (PPATHREC) NULL;
  473. // We're prepending this to the start of the first figure, so clean up
  474. // the flags:
  475. pprTargStart->flags &= ~PD_BEGINSUBPATH;
  476. pprSrcEnd->flags &= ~(PD_ENDSUBPATH | PD_CLOSEFIGURE);
  477. }
  478. /******************************Public*Routine******************************\
  479. * WIDEPATHOBJ::vReverseConcatenate(wpo)
  480. *
  481. * Reverses 'wpo' and adds all points to the end of this path.
  482. * bBeginFigure() must have been called previously for this path, and
  483. * 'wpo' must be ended by calling vEndFigure().
  484. *
  485. * The path records of 'wpo' are freed as they are copied (in order to
  486. * decrease the total number of path records needed by both paths). As such,
  487. * 'wpo' must be entirely one figure!
  488. *
  489. * History:
  490. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  491. * Wrote it.
  492. \**************************************************************************/
  493. VOID WIDEPATHOBJ::vReverseConcatenate(WIDEPATHOBJ& wpo)
  494. {
  495. #ifdef DEBUG_WIDE
  496. ASSERTGDI(bOpenPath, "vReverseConcatenate on closed target!");
  497. ASSERTGDI(!wpo.bOpenPath, "vReverseConcatenate on open source!");
  498. #endif
  499. ASSERTGDI(bValid(), "Reverse invalid path");
  500. ASSERTGDI(wpo.ppath->pprfirst != (PPATHREC) NULL, "NULL path 1");
  501. ASSERTGDI(wpo.ppath->ppachain != (PPATHALLOC) NULL, "NULL path 2");
  502. ASSERTGDI(ppath->pprfirst != (PPATHREC) NULL, "NULL path 3");
  503. ASSERTGDI(ppath->ppachain != (PPATHALLOC) NULL, "NULL path 4");
  504. // Reverse the path:
  505. PPATHREC ppr = wpo.ppath->pprlast;
  506. while (ppr != (PPATHREC) NULL)
  507. {
  508. PPATHREC pprPrev = ppr->pprprev;
  509. PPOINTFIX pptfxStart;
  510. PPOINTFIX pptfxEnd;
  511. // Copy all the points in reverse order:
  512. pptfxStart = &ppr->aptfx[0];
  513. pptfxEnd = &ppr->aptfx[ppr->count];
  514. while (pptfxEnd > pptfxStart)
  515. vAddPoint(--pptfxEnd);
  516. PPATHALLOC ppaEnd = wpo.ppath->ppachain;
  517. ASSERTGDI(ppaEnd != (PPATHALLOC) NULL, "Null pathalloc");
  518. wpo.ppath->ppachain = ppaEnd->ppanext;
  519. ASSERTGDI(((PBYTE) pptfxStart > (PBYTE) ppaEnd) &&
  520. ((PBYTE) pptfxStart - (PBYTE) ppaEnd) < (LONG) ppaEnd->siztPathAlloc,
  521. "Last pathrec doesn't correspond to last pathalloc");
  522. freepathalloc(ppaEnd);
  523. ppr = pprPrev;
  524. }
  525. ASSERTGDI(wpo.ppath->ppachain == (PPATHALLOC) NULL,
  526. "Didn't delete entire chain");
  527. // Clean up old path:
  528. wpo.ppath->ppachain = (PPATHALLOC) NULL;
  529. wpo.ppath->pprlast = (PPATHREC) NULL;
  530. wpo.ppath->pprfirst = (PPATHREC) NULL;
  531. }
  532. /******************************Public*Routine******************************\
  533. * vHalve(vec)
  534. *
  535. * Halves the vector, and always rounds 1/32 up. Symmetric about zero.
  536. *
  537. * History:
  538. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  539. * Wrote it.
  540. \**************************************************************************/
  541. INLINE VOID vHalve(EVECTORFX& vec)
  542. {
  543. if (vec.x >= 0)
  544. vec.x++;
  545. if (vec.y >= 0)
  546. vec.y++;
  547. vec.x >>= 1;
  548. vec.y >>= 1;
  549. }
  550. /******************************Public*Routine******************************\
  551. * vVecSymmetricRound(pvec)
  552. *
  553. * Always rounds 1/2 up. Symmetric about zero.
  554. *
  555. * History:
  556. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  557. * Wrote it.
  558. \**************************************************************************/
  559. INLINE VOID vVecSymmetricRound(PEVECTORFX pvec)
  560. {
  561. // Round 1/2 up:
  562. if (pvec->x >= 0)
  563. pvec->x = (pvec->x + 4) & ~7L;
  564. else
  565. pvec->x = (pvec->x + 3) & ~7L;
  566. if (pvec->y >= 0)
  567. pvec->y = (pvec->y + 4) & ~7L;
  568. else
  569. pvec->y = (pvec->y + 3) & ~7L;
  570. }
  571. /******************************Public*Routine******************************\
  572. * vVecRound(pvec)
  573. *
  574. * Always rounds 1/2 down for 'x'. Rounds 1/2 up for 'y'.
  575. *
  576. * History:
  577. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  578. * Wrote it.
  579. \**************************************************************************/
  580. INLINE VOID vVecRound(PEVECTORFX pvec)
  581. {
  582. // Round 1/2 down:
  583. if (pvec->x >= 0)
  584. pvec->x = (pvec->x + 3) & ~7L;
  585. else
  586. pvec->x = (pvec->x + 4) & ~7L;
  587. // Round 1/2 up:
  588. if (pvec->y >= 0)
  589. pvec->y = (pvec->y + 4) & ~7L;
  590. else
  591. pvec->y = (pvec->y + 3) & ~7L;
  592. }
  593. /******************************Public*Routine******************************\
  594. * WIDEPENOBJ::bHobbyize(avecAxis[])
  595. *
  596. * Will copy a pre-computed Hobby pen polygon if the pen is small and
  597. * circular.
  598. *
  599. * History:
  600. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  601. * Wrote it.
  602. \**************************************************************************/
  603. BOOL WIDEPENOBJ::bHobbyize(EVECTORFX avecAxis[])
  604. {
  605. ASSERTGDI(((avecAxis[0].x == avecAxis[1].y) && (avecAxis[0].y == -avecAxis[1].x))
  606. || ((avecAxis[0].x == -avecAxis[1].y) && (avecAxis[0].y == avecAxis[1].x)),
  607. "Must be orthogonal and circular");
  608. ASSERTGDI((MAX(ABS(avecAxis[0].x), ABS(avecAxis[0].y))
  609. < LPLUSHALFTOFX(HOBBY_TABLE_SIZE)), "Must be Hobby sized");
  610. // We now know that the transform was orthogonal, and that the pen is
  611. // circular in device space.
  612. // We now know that the vector coordinates have less than 15 bits
  613. // signficance, so we can safely compute its Euclidean length in
  614. // 32 bits:
  615. FIX fxPenWidthSquared = avecAxis[0].x * avecAxis[0].x
  616. + avecAxis[0].y * avecAxis[0].y;
  617. if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(HOBBY_TABLE_SIZE)))
  618. {
  619. // Pen is small enough that it will be in our Hobby tables:
  620. LONG iHobbyPen;
  621. if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(1)))
  622. iHobbyPen = 0; // Width less than 1.5
  623. else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(2)))
  624. iHobbyPen = 1; // Width less than 2.5
  625. else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(3)))
  626. iHobbyPen = 2; // Width less than 3.5
  627. else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(4)))
  628. iHobbyPen = 3; // Width less than 4.5
  629. else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(5)))
  630. iHobbyPen = 4; // Width less than 5.5
  631. else
  632. {
  633. #if (HOBBY_TABLE_SIZE != 6L)
  634. #error "Table size changed, update routine"
  635. #endif
  636. iHobbyPen = 5; // Width less than 6.5
  637. }
  638. // Copy the Hobby pen from the table to our path:
  639. if (!bBeginFigure())
  640. return(FALSE);
  641. PPOINTFIX pptfx = ahob[iHobbyPen].pptfx;
  642. PPOINTFIX pptfxEnd = pptfx + ahob[iHobbyPen].cptfx;
  643. while (pptfx < pptfxEnd)
  644. vAddPoint(pptfx++);
  645. vEndFigure();
  646. bIsHobby(TRUE);
  647. return(TRUE);
  648. }
  649. return(FALSE);
  650. }
  651. /******************************Public*Routine******************************\
  652. * WIDEPENOBJ::bThicken(aptfxDiameter)
  653. *
  654. * Check if the pen is so thin in one dimension that it would be invisible
  655. * for some lines. If so, thicken it.
  656. *
  657. * History:
  658. * 31-Jan-1992 -by- J. Andrew Goossen [andrewgo]
  659. * Wrote it.
  660. \**************************************************************************/
  661. #define FX_HALF (LTOFX(1) >> 1)
  662. #define FX_HALF_SQUARED (FX_HALF * FX_HALF)
  663. BOOL WIDEPENOBJ::bThicken(PPOINTFIX aptfxDiameter)
  664. {
  665. // The aptfxDiameter vectors passed to us were constructed from the
  666. // diameter of the pen -- we actually want the vectors as based on
  667. // the radius, so halve them:
  668. EVECTORFX aevecTmp[2];
  669. PPOINTFIX aptfx = (PPOINTFIX) aevecTmp;
  670. aevecTmp[0] = aptfxDiameter[0];
  671. aevecTmp[1] = aptfxDiameter[1];
  672. vHalve(aevecTmp[0]);
  673. vHalve(aevecTmp[1]);
  674. // What I would like to do is compute the length of the minor-axis of
  675. // the pen ellipse in device coordinates -- if its length is less than
  676. // half a pixel, we know we have to thicken the pen.
  677. //
  678. // But it's a lot of computation to do that (see Knuth, "Metafont",
  679. // p. 382). So, we take the two vectors that described the axis of
  680. // the ellipse in world space, and transform them to device space (call
  681. // them 'avec'). The perpendicular distance from the smaller vector
  682. // to the larger will be close to the minor-axis length.
  683. // Make sure we won't overflow:
  684. if (((ABS(aptfx[0].x) | ABS(aptfx[0].y) | ABS(aptfx[1].x) | ABS(aptfx[1].y))
  685. & ~(0xfffL)) != 0)
  686. return(FALSE);
  687. FIX fxLengthSquared0 = aptfx[0].x * aptfx[0].x + aptfx[0].y * aptfx[0].y;
  688. FIX fxLengthSquared1 = aptfx[1].x * aptfx[1].x + aptfx[1].y * aptfx[1].y;
  689. POINTFIX ptfx; // Will be the largest vector
  690. FIX fxLength; // Length of largest vector
  691. //
  692. // We use the forumla to compute the distance from a point to a line
  693. // from the origin, where 'd' is the distance, 'B' is the vector,
  694. // 'P' is the point:
  695. //
  696. // 2
  697. // 2 4 [ (P.y * B.x) - (P.x * B.y) ]
  698. // d = -------------------------------
  699. // B.x * B.x + B.y * B.y
  700. //
  701. // We want to exit early when the distance is more than 1/2 pixel (8 in
  702. // FIX units), i.e. HALF < d or:
  703. //
  704. // 2 2 2 2
  705. // HALF (B.x + B.y ) < 4 [ (P.y * B.x) - (P.x * B.y) ]
  706. //
  707. if (fxLengthSquared0 > fxLengthSquared1)
  708. {
  709. // Vector 0 is bigger than 1:
  710. FIX fxNum = aptfx[1].y * aptfx[0].x - aptfx[1].x * aptfx[0].y;
  711. LONGLONG eqLeft = (LONGLONG) fxLengthSquared0 * (FX_HALF_SQUARED >> 2);
  712. LONGLONG eqRight = Int32x32To64((LONG) fxNum, (LONG) fxNum);
  713. if (eqLeft < eqRight)
  714. return(FALSE);
  715. ptfx = aptfx[0];
  716. fxLength = fxLengthSquared0;
  717. }
  718. else
  719. {
  720. // Vector 1 is bigger than (or equal to) 0:
  721. FIX fxNum = aptfx[0].y * aptfx[1].x - aptfx[0].x * aptfx[1].y;
  722. LONGLONG eqLeft = (LONGLONG) fxLengthSquared1 * (FX_HALF_SQUARED >> 2);
  723. LONGLONG eqRight = Int32x32To64((LONG) fxNum, (LONG) fxNum);
  724. if (eqLeft < eqRight)
  725. return(FALSE);
  726. ptfx = aptfx[1];
  727. fxLength = fxLengthSquared1;
  728. }
  729. // Make sure that the largest vector extends outside the unit circle (if
  730. // it's not, we'll make it so that we end up with the entire Hobby pen
  731. // for width 1):
  732. if (fxLength < FX_HALF_SQUARED)
  733. {
  734. ptfx.x = FX_HALF;
  735. ptfx.y = 0;
  736. }
  737. // Okay, we know the larger vector, and we have to fix the smaller one.
  738. // Let's simply figure out the vertex on the Hobby pen of width 1 that
  739. // would be used if we drew a line in the same direction as our large
  740. // vector (which is when our given pen is its thinest).
  741. //
  742. // So let's compute the appropriate Hobby vector of size one (making
  743. // sure it's to the 'left' of the big vector, because the vertices have to
  744. // be in counter-clockwise order):
  745. POINTFIX ptfxHobby;
  746. if (ABS(ptfx.y) <= ptfx.x)
  747. {
  748. ptfxHobby.x = 0;
  749. ptfxHobby.y = -FX_HALF;
  750. }
  751. else if (ABS(ptfx.x) <= -ptfx.y)
  752. {
  753. ptfxHobby.x = -FX_HALF;
  754. ptfxHobby.y = 0;
  755. }
  756. else if (ABS(ptfx.y) <= -ptfx.x)
  757. {
  758. ptfxHobby.x = 0;
  759. ptfxHobby.y = FX_HALF;
  760. }
  761. else
  762. {
  763. ASSERTGDI(ABS(ptfx.x) <= ptfx.y, "Oops");
  764. ptfxHobby.x = FX_HALF;
  765. ptfxHobby.y = 0;
  766. }
  767. // Put stuff into path, remembering that 'pptfx' points to either
  768. // ppath->pprlast->aptfx[0] or [1]. Also, we have to be in counter-
  769. // clockwise order.
  770. if (!bBeginFigure())
  771. return(FALSE);
  772. vAddPoint(&ptfx);
  773. vAddPoint(&ptfxHobby);
  774. ptfx.x = -ptfx.x;
  775. ptfx.y = -ptfx.y;
  776. ptfxHobby.x = -ptfxHobby.x;
  777. ptfxHobby.y = -ptfxHobby.y;
  778. vAddPoint(&ptfx);
  779. vAddPoint(&ptfxHobby);
  780. vEndFigure();
  781. return(TRUE);
  782. }
  783. /******************************Public*Routine******************************\
  784. * WIDEPENOBJ::bPenFlatten(pptfxControl)
  785. *
  786. * Flattens a geometric pen object. pptfxControl points to the 7 control
  787. * points of a half-ellipse.
  788. *
  789. * History:
  790. * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  791. * Wrote it.
  792. \**************************************************************************/
  793. BOOL WIDEPENOBJ::bPenFlatten(PPOINTFIX pptfxControl)
  794. {
  795. if (!bGrowPath())
  796. return(FALSE);
  797. ASSERTGDI(pptfxPathRecEnd - pptfxPathRecCurrent > 2, "Path rec too small");
  798. ppath->pprlast->flags = PD_BEGINSUBPATH;
  799. // Leave room to insert a point at the beginning of the path. Also leave
  800. // room for an additional point at the end of the path record:
  801. PPOINTFIX pptfxFirst = pptfxPathRecCurrent;
  802. pptfxPathRecCurrent++;
  803. pptfxPathRecEnd--;
  804. // Don't forget the very first point:
  805. *pptfxPathRecCurrent++ = *pptfxControl;
  806. // We'll crack 2 Beziers:
  807. for (LONG ll = 0; ll <= 1; ll++, pptfxControl += 3)
  808. {
  809. BOOL bMore;
  810. BEZIER bez;
  811. bez.vInit(pptfxControl);
  812. do
  813. {
  814. bMore = bez.bNext(pptfxPathRecCurrent++);
  815. if (!bMore)
  816. break;
  817. // Check if we have to create a new path record:
  818. if (pptfxPathRecCurrent > pptfxPathRecEnd)
  819. {
  820. // Wrap up old path record:
  821. ASSERTGDI(pptfxPathRecCurrent == pptfxPathRecEnd + 1,
  822. "Cur != End + 1");
  823. // We want to repeat the last edge of this path record as the
  824. // first edge in the next. That means repeating two points
  825. // (remember that we left room for one more point in this record):
  826. PPOINTFIX pptfxPrev = pptfxPathRecCurrent - 2;
  827. // Sundown truncation
  828. ppath->pprlast->count = (ULONG)(pptfxPathRecCurrent
  829. - ppath->pprlast->aptfx);
  830. ASSERTGDI(ppath->pprlast->count >= 3,
  831. "Pen pathrecord doesn't have 3 vertices");
  832. ppath->ppachain->pprfreestart = NEXTPATHREC(ppath->pprlast);
  833. // Create a new path record:
  834. if (!bGrowPath())
  835. return(FALSE);
  836. ppath->pprlast->flags = 0;
  837. // Repeat those two points in the new path record:
  838. *pptfxPathRecCurrent++ = *(pptfxPrev);
  839. *pptfxPathRecCurrent++ = *(pptfxPrev + 1);
  840. // Don't forget to leave room for an additional point at the
  841. // end of the record:
  842. pptfxPathRecEnd--;
  843. }
  844. } while (1);
  845. }
  846. // The pen covers 180 degrees of the ellipse. The first and last edges
  847. // of the pen must be parallel, their vectors in opposite directions.
  848. // The last point in the path is already the opposite of the first. Now
  849. // make the zeroth point the opposite of the second last and voila,
  850. // parallel edges.
  851. pptfxFirst->x = - (pptfxPathRecCurrent - 2)->x;
  852. pptfxFirst->y = - (pptfxPathRecCurrent - 2)->y;
  853. ppath->pprlast->flags |= PD_ENDSUBPATH;
  854. // Sundown truncation
  855. ppath->pprlast->count = (ULONG)(pptfxPathRecCurrent - ppath->pprlast->aptfx);
  856. ASSERTGDI(ppath->pprlast->count >= 3,
  857. "Final pen pathrecord doesn't have 3 vertices");
  858. return(TRUE);
  859. }
  860. /******************************Public*Routine******************************\
  861. * WIDEPENOBJ::bPolygonizePen(exo, ulWidth)
  862. *
  863. * Creates a polyline approximation to the pen.
  864. *
  865. * If the pen is small and circular, we copy a pre-computed Hobby pen;
  866. * otherwise, we approximate the pen using Bezier curves and convert this
  867. * to lines. Remembering that the pen is defined in world space, we
  868. * construct the Bezier curves that approximate the pen circle in world
  869. * space. We then transform the coordinates to device space, and flatten.
  870. *
  871. * It makes life a lot easier if we can assume that the vertices of the pen
  872. * are stored in counter-clockwise order. So, we check the transform to
  873. * see if it is inverting, and adjust the Bezier control points accordingly.
  874. *
  875. * Due to the half-symmetry property of ellipses, we only have to store
  876. * the vertices for half the ellipse. For the binary search, it is useful
  877. * also to ensure that the last edge vector is the exact opposite of the
  878. * first.
  879. *
  880. * History:
  881. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  882. * Wrote it.
  883. \**************************************************************************/
  884. BOOL WIDEPENOBJ::bPolygonizePen(
  885. EXFORMOBJ& exo, // World to device transform
  886. LONG lWidth) // Pen width in world space
  887. {
  888. EVECTORFX avecAxis[2];
  889. avecAxis[0].x = (FIX) lWidth;
  890. avecAxis[0].y = 0;
  891. avecAxis[1].x = 0;
  892. avecAxis[1].y = - (FIX) lWidth;
  893. exo.bXform((PVECTORL) avecAxis, avecAxis, 2);
  894. if (((avecAxis[0].x == avecAxis[1].y) && (avecAxis[0].y == -avecAxis[1].x))
  895. || ((avecAxis[0].x == -avecAxis[1].y) && (avecAxis[0].y == avecAxis[1].x)))
  896. {
  897. // We now know that the transform was orthogonal, and that the pen is
  898. // circular in device space.
  899. if (MAX(ABS(avecAxis[0].x), ABS(avecAxis[0].y))
  900. < LPLUSHALFTOFX(HOBBY_TABLE_SIZE))
  901. {
  902. if (bHobbyize(avecAxis))
  903. return(TRUE);
  904. else if (!bValid()) // See if path still okay
  905. return(FALSE);
  906. }
  907. }
  908. if (bThicken((PPOINTFIX) avecAxis))
  909. return(TRUE);
  910. else if (!bValid()) // See if path still okay
  911. return(FALSE);
  912. // The points must be ordered in a counter-clockwise direction.
  913. // The half-ellipse we're creating starts at avecAxis[0], goes through
  914. // avecAxis[1], and ends at -avecAxis[0]. As such, avecAxis[1] must
  915. // lie to the left of avecAxis[0]:
  916. if (!bTurnLeftRandom(&avecAxis[0], &avecAxis[1]))
  917. {
  918. avecAxis[1].x = -avecAxis[1].x;
  919. avecAxis[1].y = -avecAxis[1].y;
  920. }
  921. vHalve(avecAxis[0]);
  922. vHalve(avecAxis[1]);
  923. VECTORFX vecRight;
  924. VECTORFX vecUp;
  925. LONGLONG eqTmp;
  926. vEllipseControlsOut(&avecAxis[0], &vecRight, &eqTmp);
  927. vEllipseControlsOut(&avecAxis[1], &vecUp, &eqTmp);
  928. //
  929. // 4 3 2
  930. // +----x-------E-------x----+
  931. // | |
  932. // | |
  933. // | |
  934. // 5 x x 1 ^
  935. // | | |
  936. // | | |
  937. // | | | vecUp
  938. // | VecRight | |
  939. // 6 E O-------> E 0 |
  940. //
  941. // where 'O' is the origin,
  942. // 'x' is a control point of a Bezier curve
  943. // 'E' is an end point of a Bezier curve
  944. //
  945. EVECTORFX avec[7];
  946. avec[6].x = -avecAxis[0].x;
  947. avec[6].y = -avecAxis[0].y;
  948. avec[5] = avec[6];
  949. avec[5] += vecUp;
  950. avec[4] = avecAxis[1];
  951. avec[4] -= vecRight;
  952. avec[3] = avecAxis[1];
  953. avec[2] = avecAxis[1];
  954. avec[2] += vecRight;
  955. avec[1] = avecAxis[0];
  956. avec[1] += vecUp;
  957. avec[0] = avecAxis[0];
  958. return(bPenFlatten((PPOINTFIX) avec));
  959. }
  960. /******************************Public*Routine******************************\
  961. * bLeft(pvec, pptfx, pfxCross)
  962. *
  963. * Returns TRUE if the vector to 'pptfx' turns left from 'pvec'. Returns
  964. * the cross product in pfxCross.
  965. *
  966. * NOTE: Since only the low DWORD of the cross product is returned, *pfxCross
  967. * is valid only when the vector '*(pptfx + 1) - *pptfx' is close to being
  968. * parallel to 'pvec'.
  969. *
  970. * History:
  971. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  972. * Wrote it.
  973. \**************************************************************************/
  974. INLINE BOOL bLeft(
  975. PEVECTORFX pvec, // First vector
  976. PPOINTFIX pptfx, // Second vector
  977. PLONGLONG pfxCross) // Result of pvec X pptfx
  978. {
  979. LONGLONG ad;
  980. LONGLONG bc;
  981. ad = Int32x32To64(pvec->x, (pptfx + 1)->y - pptfx->y);
  982. bc = Int32x32To64(pvec->y, (pptfx + 1)->x - pptfx->x);
  983. ad -= bc;
  984. *pfxCross = ad;
  985. return(ad < 0);
  986. }
  987. /******************************Public*Routine******************************\
  988. * WIDEPENOBJ::vDetermineDrawVertex(vec, ld)
  989. *
  990. * The core routine of wide-lines. This routine determines the point
  991. * on the pen that draws the wide-line outline for the given line vector.
  992. *
  993. * Think of the line as a ray, and imagine dragging an elliptical pen
  994. * along it. The point on the ellipse that is furthest from the
  995. * ray defines the outline of the widened line. It's a bit of work to
  996. * determine the draw vertex on a true ellipse (involving a bunch of
  997. * floating point calculations).
  998. *
  999. * We take a different approach: we've already computed a convex polygonal
  1000. * approximation of the true ellipse. We search the vertices of the
  1001. * polygon to determine which is the farthest from the ray.
  1002. *
  1003. * We could do this by finding the maximum cross product of the line
  1004. * vector and the vectors from the center of the pen to each of its
  1005. * vertices, but this approach doesn't lend itself to a binary search.
  1006. *
  1007. * We accomplish a binary search by looking at the cross product of the
  1008. * line vector and the vector difference between successive vertices of the
  1009. * pen polygon -- we find the point on the pen polygon whose adjacent
  1010. * edge vector on one side turns to the 'left' of the line vector, and
  1011. * whose other edge vector turns to the 'right' of the line vector.
  1012. * That is, the vertex whose cross products of its adjacent edge vectors
  1013. * and the line vector have different signs.
  1014. *
  1015. * Our pen polygon is only half the ellipse -- under an affine transform,
  1016. * ellipses are always symmetrical about some axis. Since we made sure
  1017. * the first edge is exactly opposite the last edge when we built the
  1018. * pen polygon, it follows that for any line vector, the cross product
  1019. * with the first and last edges have different signs (unless the line
  1020. * vector is parallel to that edge, which we sort of let fall through).
  1021. * So we start the binary search by considering the first and last edges
  1022. * of the half-ellipse.
  1023. *
  1024. * For the binary search, we maintain the invariant that the cross product
  1025. * of the first and last edges with our line vector has different signs, and
  1026. * we subdivide the interval between the first and last edge until they are
  1027. * adjacent edges on the polygon; the shared vertex is then our draw vertex.
  1028. *
  1029. * History:
  1030. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  1031. * Wrote it.
  1032. \**************************************************************************/
  1033. VOID WIDEPENOBJ::vDetermineDrawVertex(
  1034. EVECTORFX& vec, // Direction of line
  1035. LINEDATA& ld) // Result goes here
  1036. {
  1037. PPOINTFIX pptfxStart;
  1038. PPOINTFIX pptfxEnd;
  1039. PPOINTFIX pptfxMid;
  1040. LONGLONG fxCrossMid;
  1041. BOOL bLeftStart;
  1042. BOOL bLeftEnd;
  1043. ld.ppr = ppath->pprfirst;
  1044. if (ppath->pprfirst == ppath->pprlast)
  1045. {
  1046. bLeftStart = bLeft(&vec, &ld.ppr->aptfx[0], &ld.fxCrossStart);
  1047. ld.fxCrossEnd = -ld.fxCrossStart;
  1048. }
  1049. else
  1050. {
  1051. // El yucko, the pen is bigger than one PathRecord. Scan through the
  1052. // linked list until we find a PathRecord whose edges bracket our vector.
  1053. //
  1054. // NOTE: If our vector is parallel to and in the same direction as the
  1055. // last edge in the last pathrecord, then
  1056. //
  1057. // bTurnLeft(vec, vecStart) == FALSE
  1058. // and bTurnLeft(vec, vecEnd) == FALSE
  1059. while (TRUE)
  1060. {
  1061. bLeftStart = bLeft(&vec,
  1062. &ld.ppr->aptfx[0],
  1063. &ld.fxCrossStart);
  1064. bLeftEnd = bLeft(&vec,
  1065. &ld.ppr->aptfx[ld.ppr->count - 2],
  1066. &ld.fxCrossEnd);
  1067. if ((bLeftStart ^ bLeftEnd) || ld.ppr->pprnext == (PPATHREC) NULL)
  1068. break;
  1069. ld.ppr = ld.ppr->pprnext;
  1070. }
  1071. }
  1072. if (bLeftStart)
  1073. ld.vSetInvert();
  1074. else
  1075. ld.vClearInvert();
  1076. // 'pptfxStart' is a pointer to the first point of the first edge in
  1077. // the path record. 'pptfxEnd' is a pointer to the first point of the
  1078. // last edge.
  1079. pptfxStart = &ld.ppr->aptfx[0];
  1080. pptfxEnd = &ld.ppr->aptfx[ld.ppr->count - 2];
  1081. while (TRUE)
  1082. {
  1083. pptfxMid = pptfxStart + ((pptfxEnd - pptfxStart) >> 1);
  1084. if (bLeft(&vec, pptfxMid, &fxCrossMid) ^ bLeftStart)
  1085. {
  1086. // Signs are different, so the point we want must be between
  1087. // 'start' and 'mid':
  1088. ld.fxCrossEnd = fxCrossMid;
  1089. if (pptfxMid == pptfxStart + 1)
  1090. {
  1091. ld.pptfx = pptfxMid;
  1092. break;
  1093. }
  1094. pptfxEnd = pptfxMid;
  1095. }
  1096. else
  1097. {
  1098. // Signs are the same, so the point we want must be between
  1099. // 'mid' and 'end':
  1100. ld.fxCrossStart = fxCrossMid;
  1101. if (pptfxEnd == pptfxMid + 1)
  1102. {
  1103. ld.pptfx = pptfxEnd;
  1104. break;
  1105. }
  1106. pptfxStart = pptfxMid;
  1107. }
  1108. ASSERTGDI(pptfxStart < pptfxEnd, "Couldn't find draw vertex");
  1109. }
  1110. // ld.pptfx is our draw vertex.
  1111. #ifdef DEBUG_WIDE
  1112. ASSERTGDI((ld.fxCrossEnd >= 0 && ld.fxCrossStart < 0) ||
  1113. (ld.fxCrossEnd <= 0 && ld.fxCrossStart >= 0),
  1114. "Expected different cross products signs");
  1115. #endif
  1116. ld.fxCrossStart = ABS(ld.fxCrossStart);
  1117. ld.fxCrossEnd = ABS(ld.fxCrossEnd);
  1118. }
  1119. /******************************Public*Routine******************************\
  1120. * vAddNice(wpath, pptfx, pvec, bInvert)
  1121. *
  1122. * This routine is for adding round joins and end-caps to the given path.
  1123. *
  1124. * It does a trick to make rounds look nicer: when the given point lies on
  1125. * the integer grid, it shrinks the pen vector by 1/16th of a pel in both
  1126. * coordinates.
  1127. *
  1128. * This works on the assumption that most often the pens are constructed
  1129. * from integer widths in device space -- when that happens, we will often
  1130. * get assymetric filling because our fill convention breaks ties when a
  1131. * scan-line has a line fall exactly on the integer grid by being lower-right
  1132. * exclusive.
  1133. *
  1134. * Note: Remember that the bounds calculation has to take this possible
  1135. * size increase into account.
  1136. *
  1137. * History:
  1138. * 20-Nov-1991 -by- J. Andrew Goossen [andrewgo]
  1139. * Wrote it.
  1140. \**************************************************************************/
  1141. VOID vAddNice(
  1142. WIDEPATHOBJ& wpath, // Path to add to
  1143. PPOINTFIX pptfx, // Spine point about which the round is added
  1144. PEVECTORFX pvec, // The vector to add to pptfx
  1145. BOOL bInvert) // TRUE if pvec is to be subtracted from pptfx
  1146. {
  1147. EVECTORFX vec;
  1148. if (((pptfx->x | pptfx->y) & (LTOFX(1) - 1)) == 0)
  1149. {
  1150. if (bInvert)
  1151. {
  1152. vec.x = -pvec->x;
  1153. vec.y = -pvec->y;
  1154. bInvert = FALSE;
  1155. }
  1156. else
  1157. {
  1158. vec = *pvec;
  1159. }
  1160. if (vec.x > 0)
  1161. vec.x -= 1;
  1162. else if (vec.x < 0)
  1163. vec.x += 1;
  1164. if (vec.y > 0)
  1165. vec.y -= 1;
  1166. else if (vec.y < 0)
  1167. vec.y += 1;
  1168. pvec = &vec;
  1169. }
  1170. wpath.vAddPoint(pptfx, pvec, bInvert);
  1171. }
  1172. /******************************Public*Routine******************************\
  1173. * WIDEPENOBJ::cptAddRound(wid, ldIn, ldOut, bLeft, bInPerp, bOutPerp)
  1174. *
  1175. * Adds round joins to 'wid'.
  1176. *
  1177. * History:
  1178. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  1179. * Wrote it.
  1180. \**************************************************************************/
  1181. COUNT WIDEPENOBJ::cptAddRound(
  1182. WIDENER& wid, // Path to which to add
  1183. LINEDATA& ldIn, // Vector entering the point
  1184. LINEDATA& ldOut, // Vector exiting the point
  1185. BOOL bLeft, // ldOut turns left from ldIn?
  1186. BOOL bInPerp, //
  1187. BOOL bOutPerp) //
  1188. {
  1189. COUNT cpt = 0;
  1190. ASSERTGDI(ldIn.bVecDrawComputed() && ldOut.bVecDrawComputed(),
  1191. "Draw vertex not computed");
  1192. if (ldIn.pptfx == ldOut.pptfx && ldIn.bInvert() == ldOut.bInvert())
  1193. return(0);
  1194. BOOL bInvert = ldIn.bInvert();
  1195. PPATHREC ppr = ldIn.ppr;
  1196. PPOINTFIX pptfx = ldIn.pptfx;
  1197. if (bLeft)
  1198. {
  1199. // Turns left, so add counter-clockwise to right side:
  1200. if (bInPerp && !ldIn.bToLeftSide())
  1201. {
  1202. wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
  1203. }
  1204. pptfx++;
  1205. while (ppr != ldOut.ppr ||
  1206. pptfx > ldOut.pptfx ||
  1207. bInvert != ldOut.bInvert())
  1208. {
  1209. PPOINTFIX pptfxEnd = &ppr->aptfx[ldIn.ppr->count - 1];
  1210. while (pptfx < pptfxEnd)
  1211. {
  1212. wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
  1213. cpt++;
  1214. }
  1215. if (ppr->pprnext != (PPATHREC) NULL)
  1216. ppr = ppr->pprnext;
  1217. else
  1218. {
  1219. ppr = ppath->pprfirst;
  1220. bInvert = !bInvert;
  1221. }
  1222. pptfx = &ppr->aptfx[1];
  1223. }
  1224. ASSERTGDI(ppr == ldOut.ppr, "Unmatched path record pointers");
  1225. ASSERTGDI(ldOut.pptfx - pptfx < (PATHALLOCSIZE >> 3),
  1226. "Clockwise join unsynced");
  1227. while (pptfx < ldOut.pptfx)
  1228. {
  1229. wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
  1230. cpt++;
  1231. }
  1232. if (bOutPerp && ldOut.bToLeftSide())
  1233. {
  1234. wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
  1235. }
  1236. }
  1237. else
  1238. {
  1239. // Turns right, so add clockwise to left side:
  1240. if (bInPerp && ldIn.bToLeftSide())
  1241. {
  1242. wid.vAddLeftNice((PEVECTORFX) pptfx, bInvert);
  1243. }
  1244. pptfx--;
  1245. while (ppr != ldOut.ppr ||
  1246. pptfx < ldOut.pptfx ||
  1247. bInvert != ldOut.bInvert())
  1248. {
  1249. PPOINTFIX pptfxStart = &ppr->aptfx[1];
  1250. while (pptfx > pptfxStart)
  1251. {
  1252. wid.vAddLeftNice((PEVECTORFX) pptfx--, bInvert);
  1253. cpt++;
  1254. }
  1255. if (ppr->pprprev != (PPATHREC) NULL)
  1256. ppr = ppr->pprprev;
  1257. else
  1258. {
  1259. ppr = ppath->pprlast;
  1260. bInvert = !bInvert;
  1261. }
  1262. pptfx = &ppr->aptfx[ppr->count - 1];
  1263. }
  1264. ASSERTGDI(ppr == ldOut.ppr, "Unmatched path record pointers");
  1265. ASSERTGDI(pptfx - ldOut.pptfx < (PATHALLOCSIZE >> 3),
  1266. "Counterclockwise join unsynced");
  1267. while (pptfx > ldOut.pptfx)
  1268. {
  1269. wid.vAddLeftNice((PEVECTORFX) pptfx--, bInvert);
  1270. cpt++;
  1271. }
  1272. if (bOutPerp && !ldOut.bToLeftSide())
  1273. {
  1274. wid.vAddLeftNice((PEVECTORFX) pptfx, bInvert);
  1275. }
  1276. }
  1277. return(cpt);
  1278. }
  1279. /******************************Public*Routine******************************\
  1280. * WIDEPENOBJ::vAddRoundEndCap(wid, ld, bStartCap, bRound)
  1281. *
  1282. * Adds round caps to the path.
  1283. *
  1284. * History:
  1285. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  1286. * Wrote it.
  1287. \**************************************************************************/
  1288. VOID WIDEPENOBJ::vAddRoundEndCap(
  1289. WIDENER& wid, // Path to which to add
  1290. LINEDATA& ld, // Vector entering the point
  1291. BOOL bStartCap, // TRUE if Start-cap
  1292. BOOL bRound) // TRUE if round joins
  1293. {
  1294. ASSERTGDI(ld.bVecDrawComputed(), "Draw vertex not computed");
  1295. BOOL bInvert = (bStartCap) ? !ld.bInvert() : ld.bInvert();
  1296. PPATHREC ppr = ld.ppr;
  1297. PPOINTFIX pptfx = ld.pptfx;
  1298. // Turns left, so add counter-clockwise to right side:
  1299. if (!bRound && !ld.bToLeftSide())
  1300. wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
  1301. pptfx++;
  1302. while (ppr != ld.ppr ||
  1303. pptfx > ld.pptfx)
  1304. {
  1305. PPOINTFIX pptfxEnd = &ppr->aptfx[ld.ppr->count - 1];
  1306. while (pptfx < pptfxEnd)
  1307. wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
  1308. if (ppr->pprnext != (PPATHREC) NULL)
  1309. ppr = ppr->pprnext;
  1310. else
  1311. {
  1312. ppr = ppath->pprfirst;
  1313. bInvert = !bInvert;
  1314. }
  1315. pptfx = &ppr->aptfx[1];
  1316. }
  1317. ASSERTGDI(ppr == ld.ppr, "Unmatched path record pointers");
  1318. ASSERTGDI(ld.pptfx - pptfx < (PATHALLOCSIZE >> 3),
  1319. "Clockwise join unsynced");
  1320. while (pptfx < ld.pptfx)
  1321. wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
  1322. if (!bRound && ld.bToLeftSide())
  1323. wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
  1324. }
  1325. /******************************Public*Routine******************************\
  1326. * READER::bNextPoint(ptfx)
  1327. *
  1328. * Reads the next point from the spine of the path.
  1329. *
  1330. * History:
  1331. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1332. * Wrote it.
  1333. \**************************************************************************/
  1334. BOOL READER::bNextPoint(POINTFIX& ptfx)
  1335. {
  1336. BOOL bRet;
  1337. if (pptfxRead < pptfxEnd)
  1338. {
  1339. ptfx = *pptfxRead++;
  1340. bRet = TRUE;
  1341. }
  1342. else if (pd.flags & PD_ENDSUBPATH)
  1343. bRet = FALSE;
  1344. else
  1345. {
  1346. // Get next path record:
  1347. vMoreToEnum(pepoSpine->bEnum(&pd));
  1348. ASSERTGDI(pd.count > 0, "Empty path record");
  1349. ASSERTGDI(!(pd.flags & PD_BEGINSUBPATH), "Unexpected begin subpath");
  1350. ptfx = pd.pptfx[0];
  1351. pptfxRead = &pd.pptfx[1];
  1352. pptfxEnd = &pd.pptfx[pd.count];
  1353. bRet = TRUE;
  1354. }
  1355. return(bRet);
  1356. }
  1357. /******************************Public*Routine******************************\
  1358. * READER::bNextFigure()
  1359. *
  1360. * Goes to the next figure in the path. Returns FALSE if there are none.
  1361. *
  1362. * History:
  1363. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1364. * Wrote it.
  1365. \**************************************************************************/
  1366. BOOL READER::bNextFigure()
  1367. {
  1368. BOOL bRet = bMoreToEnum();
  1369. if (bRet)
  1370. {
  1371. vMoreToEnum(pepoSpine->bEnum(&pd));
  1372. if (pd.count == 0)
  1373. {
  1374. ASSERTGDI(!bMoreToEnum(), "Empty path record in non-empty path");
  1375. bRet = FALSE;
  1376. }
  1377. ASSERTGDI((pd.flags & PD_BEGINSUBPATH) || (pd.count == 0),
  1378. "bNextFig: Not at start of fig");
  1379. pptfxRead = &pd.pptfx[0];
  1380. pptfxEnd = &pd.pptfx[pd.count];
  1381. }
  1382. return(bRet);
  1383. }
  1384. /******************************Public*Routine******************************\
  1385. * LINER::vNextPoint()
  1386. *
  1387. * This function reads the next point in a path. It will break Bezier
  1388. * curves into lines as it goes.
  1389. *
  1390. * CONSTRUCTING THE PATH BY VERTEX INSTEAD OF BY LINE
  1391. *
  1392. * The widening system is point based and not line based. That is, we
  1393. * read the original path one vertex at-a-time and construct the wide-lines
  1394. * by making the appropriate structures at each of those vertices (i.e.,
  1395. * at every vertex we either construct an end-cap or a join); we don't read
  1396. * the path a line at-a-time and add both ends of the line then do the
  1397. * join if there is one.
  1398. *
  1399. * This is a subtle but important distinction.
  1400. *
  1401. * RETURNING THE DATA
  1402. *
  1403. * To construct a join, we need 3 things from the original path:
  1404. * 1) The point of the join;
  1405. * 2) The vector of the line entering the join;
  1406. * 3) The vector of the line exiting the join.
  1407. *
  1408. * A start cap requires:
  1409. * 1) The point of the start cap;
  1410. * 2) The vector of the line exiting the start cap.
  1411. *
  1412. * An end cap requires:
  1413. * 1) The point of the end cap;
  1414. * 2) The vector of the line entering the end cap.
  1415. *
  1416. * This data is returned by setting 'ptfxThis' as the point of interest,
  1417. * 'pldIn' points to the entering vector, and 'pldOut' points to the
  1418. * exiting vector.
  1419. *
  1420. * We don't incur the cost of re-computing the same perpendicular or draw
  1421. * vector for both sides of a line because that's handled by the lazy
  1422. * evaluation of 'pldIn' and 'pldOut'.
  1423. *
  1424. * VARIABLES
  1425. *
  1426. * Internal:
  1427. *
  1428. * ptfxNext - Next point after 'ptfxThis', used to compute 'ldOut'
  1429. * ptfxStartFigure - Start point for current figure
  1430. * ls - Internal state for reading path
  1431. * ldStartFigure - Line vector data for first line in figure
  1432. * ldStartTangent, ldEndTangent - Line vector data for start and end
  1433. * tangents of current Bezier
  1434. *
  1435. * External:
  1436. *
  1437. * ptfxThis - Current point
  1438. * pldIn, pldOut - Entering and exiting vectors from 'ptfxThis'
  1439. *
  1440. * pld->vecLine is the difference vector between 'ptfxThis' and 'ptfxNext'
  1441. * (so its length is the length of that line; used by styler)
  1442. * pld->vecTangent is the true tangent vector at the point
  1443. * (this is used for calculating true perpendiculars, and
  1444. * its length does NOT correspond to the line at that point)
  1445. *
  1446. * we - Returns point type at 'ptfxThis':
  1447. *
  1448. * WE_STARTFIGURE - Figure starts here
  1449. * WE_JOIN - Do a normal join
  1450. * WE_BEZIERJOIN - Do a Bezier join
  1451. * WE_CLOSEFIGURE - Do the final CloseFigure join
  1452. * WE_ENDFIGURE - Do the end-cap
  1453. * WE_FINISHFIGURE - Do the start-cap for the figure we
  1454. * just finished
  1455. * WE_DONEPATH - All done!
  1456. *
  1457. * The resulting language is:
  1458. *
  1459. * {s{j|b}[c|ef]}d
  1460. *
  1461. * History:
  1462. * 1-Dec-1991 -by- J. Andrew Goossen [andrewgo]
  1463. * Wrote it.
  1464. \**************************************************************************/
  1465. VOID LINER::vNextPoint()
  1466. {
  1467. // Assign 'pldOut' a new spot in the LINEDATA buffer. Just make sure
  1468. // the location isn't already being used by 'pldIn':
  1469. pldOut = &ldBuf[ (pldIn != &ldBuf[0]) ? 0 : 1 ];
  1470. switch (ls)
  1471. {
  1472. case LS_READPATH:
  1473. case LS_STARTFIGURE:
  1474. if (ls == LS_READPATH)
  1475. we = WE_JOIN; // Normal join
  1476. else
  1477. {
  1478. ASSERTGDI(ls == LS_STARTFIGURE, "Not start figure");
  1479. we = WE_STARTFIGURE; // The point starts a figure
  1480. ls = LS_READPATH;
  1481. }
  1482. if (bNextPoint(ptfxNext))
  1483. {
  1484. // The next point in the path may be the second control point
  1485. // of a Bezier curve. If it is, we have to set up to crack the
  1486. // curve into lines:
  1487. if (bIsBezier())
  1488. {
  1489. POINTFIX aptfx[4];
  1490. aptfx[0] = ptfxThis;
  1491. aptfx[1] = ptfxNext;
  1492. bNextPoint(aptfx[2]);
  1493. bNextPoint(aptfx[3]);
  1494. bez.vInit(aptfx);
  1495. if (!bez.bNext(&ptfxNext))
  1496. {
  1497. // Okay, we have a real small Bezier curve (it got converted
  1498. // into one line). Don't bother trying to use the Bezier's
  1499. // control points to get accurate perps (that is a heuristic
  1500. // that only works when you have lotsa lines in the
  1501. // approximation):
  1502. pldOut->vInit(ptfxNext, ptfxThis);
  1503. ls = LS_READPATH;
  1504. return;
  1505. }
  1506. pldOut->vInit(ptfxNext, ptfxThis);
  1507. ldStartTangent = *pldOut;
  1508. ldStartTangent.vecTangent = aptfx[1];
  1509. ldStartTangent.vecTangent -= aptfx[0];
  1510. // Initialize end-tangent vector with the true end-tangent
  1511. // (we will change 'ldEndTangent.vecLine' later):
  1512. ldEndTangent.vInit(aptfx[3], aptfx[2]);
  1513. pldOut = &ldStartTangent;
  1514. // Set up to get the next point from the current Bezier:
  1515. ls = LS_READBEZIER;
  1516. return;
  1517. }
  1518. }
  1519. else
  1520. {
  1521. // Since there is no 'ptfxNext', it follows that 'ptfxThis' is the
  1522. // last point in the figure. See if we have to close the figure.
  1523. ls = LS_FINISHFIGURE;
  1524. ptfxNext = ptfxStartFigure;
  1525. if (bIsClosedFigure())
  1526. we = WE_JOIN;
  1527. else
  1528. {
  1529. we = WE_ENDFIGURE;
  1530. return;
  1531. }
  1532. }
  1533. break;
  1534. case LS_FINISHFIGURE:
  1535. we = bIsClosedFigure() ? WE_CLOSEFIGURE : WE_FINISHFIGURE;
  1536. pldOut = &ldStartFigure;
  1537. if (!bNextFigure())
  1538. ls = LS_DONEPATH;
  1539. else
  1540. {
  1541. bNextPoint(ptfxNext);
  1542. ptfxStartFigure = ptfxNext;
  1543. ls = LS_STARTFIGURE;
  1544. }
  1545. return;
  1546. case LS_READBEZIER:
  1547. we = WE_BEZIERJOIN;
  1548. if (!bez.bNext(&ptfxNext))
  1549. {
  1550. // Since this is the last line in the Bezier, make sure we use
  1551. // the tangent vector computed from the last two control points
  1552. // when calculating perpendicular for the line:
  1553. ls = LS_READPATH;
  1554. pldOut->vInit(ptfxNext, ptfxThis);
  1555. pldOut->vecTangent = ldEndTangent.vecTangent;
  1556. return;
  1557. }
  1558. break;
  1559. case LS_DONEPATH:
  1560. we = WE_DONEPATH;
  1561. return;
  1562. default:
  1563. RIP("Unknown line state");
  1564. }
  1565. // Finally, compute the exit vector for those cases that made it here:
  1566. pldOut->vInit(ptfxNext, ptfxThis);
  1567. }
  1568. /******************************Public*Routine******************************\
  1569. * LINER::vZeroFigure()
  1570. *
  1571. * Sets pldOut and pldIn for a zero-length figure.
  1572. *
  1573. * History:
  1574. * 31-Dec-1991 -by- J. Andrew Goossen [andrewgo]
  1575. * Wrote it.
  1576. \**************************************************************************/
  1577. INLINE VOID LINER::vZeroFigure()
  1578. {
  1579. ldStartFigure.vInit();
  1580. ldStartFigure.vecLine.x = LTOFX(1);
  1581. ldStartFigure.vecTangent.x = LTOFX(1);
  1582. ldStartFigure.vecLine.y = 0;
  1583. ldStartFigure.vecTangent.y = 0;
  1584. pldIn = &ldStartFigure;
  1585. pldOut = &ldStartFigure;
  1586. }
  1587. /******************************Public*Routine******************************\
  1588. * LINER::vNextEvent()
  1589. *
  1590. * This function reads the next event from the path.
  1591. *
  1592. * History:
  1593. * 31-Dec-1991 -by- J. Andrew Goossen [andrewgo]
  1594. * Wrote it.
  1595. \**************************************************************************/
  1596. VOID LINER::vNextEvent()
  1597. {
  1598. // Update some of our current point data:
  1599. ptfxThis = ptfxNext;
  1600. pldIn = pldOut;
  1601. // Check out the next point:
  1602. vNextPoint();
  1603. WIDENEVENT weOld = we;
  1604. // Eat any zero-length lines. This will stop automatically
  1605. while (pldOut->vecLine.x == 0 && pldOut->vecLine.y == 0)
  1606. {
  1607. if (we != WE_STARTFIGURE && we != WE_JOIN && we != WE_BEZIERJOIN)
  1608. break;
  1609. // Due to rounding on the integer grid, for small Bezier curves
  1610. // the first or last line segments may wind up being zero length.
  1611. // In that case, 'vecLine' will be a zero-vector. But we do know
  1612. // the true tangents at that point.
  1613. vNextPoint();
  1614. }
  1615. // Have to watch for the case when the vecTagent is zero but vecLine is
  1616. // not (such as when the first two or last two control points of the
  1617. // Bezier are coincident):
  1618. if (pldOut->vecTangent.x == 0 && pldOut->vecTangent.y == 0)
  1619. pldOut->vecTangent = pldOut->vecLine;
  1620. if (weOld == WE_STARTFIGURE)
  1621. {
  1622. if (we == WE_CLOSEFIGURE || we == WE_ENDFIGURE)
  1623. {
  1624. if (we == WE_ENDFIGURE)
  1625. {
  1626. vNextPoint();
  1627. ASSERTGDI(we == WE_FINISHFIGURE, "Expected finish figure");
  1628. }
  1629. vZeroFigure();
  1630. we = WE_ZEROFIGURE;
  1631. }
  1632. else
  1633. {
  1634. ldStartFigure = *pldOut;
  1635. pldOut = &ldStartFigure;
  1636. we = WE_STARTFIGURE;
  1637. }
  1638. }
  1639. }
  1640. /******************************Public*Routine******************************\
  1641. * STYLER::STYLER(epo, dco)
  1642. *
  1643. * Constructor for the styler.
  1644. *
  1645. * History:
  1646. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1647. * Wrote it.
  1648. \**************************************************************************/
  1649. STYLER::STYLER(
  1650. EPATHOBJ& epo, // Path of spine
  1651. PLINEATTRS pla)
  1652. : LINER(epo),
  1653. exoDeviceToWorld(&mxDeviceToWorld, DONT_COMPUTE_FLAGS)
  1654. {
  1655. vDoingStyles((pla->pstyle != (PFLOAT_LONG) NULL) && (pla->cstyle > 0));
  1656. if (bDoingStyles())
  1657. {
  1658. pstyleStart = pla->pstyle;
  1659. pstyleCurrent = pstyleStart;
  1660. pstyleEnd = pstyleStart + pla->cstyle;
  1661. // Next point comes from the path, not from a style:
  1662. vStyleNext(FALSE);
  1663. }
  1664. }
  1665. /******************************Public*Routine******************************\
  1666. * STYLER::efNextStyleLength()
  1667. *
  1668. * Reads next style array entry from user-supplied list.
  1669. *
  1670. * History:
  1671. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1672. * Wrote it.
  1673. \**************************************************************************/
  1674. INLINE EFLOAT STYLER::efNextStyleLength()
  1675. {
  1676. EFLOATEXT efResult(pstyleCurrent->e);
  1677. pstyleCurrent++;
  1678. if (pstyleCurrent >= pstyleEnd)
  1679. pstyleCurrent = pstyleStart;
  1680. ASSERTGDI(!efResult.bIsNegative(), "Negative style length");
  1681. return(efResult);
  1682. }
  1683. /******************************Public*Routine******************************\
  1684. * STYLER::efWorldLength(vec)
  1685. *
  1686. * Computes the world space length of the vector.
  1687. *
  1688. * History:
  1689. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1690. * Wrote it.
  1691. \**************************************************************************/
  1692. EFLOAT STYLER::efWorldLength(EVECTORFX vec)
  1693. {
  1694. // Transform the vector from device FIX format to world space LONG:
  1695. BOOL bRet = exoDeviceToWorld.bXform(&vec, (PVECTORL) &vec, 1);
  1696. ASSERTGDI(bRet, "XForm failed");
  1697. // Now compute the Cartesian length:
  1698. EFLOAT x;
  1699. EFLOAT y;
  1700. // The Alpha computes an eensy-weensy length for zero-length vectors
  1701. // which causes later math to underflow, so we simply check for zero
  1702. // length here. The next smallest possible vector has a length of 1.0,
  1703. // and that has no chance of underflow.
  1704. if ((vec.x == 0) && (vec.y == 0))
  1705. {
  1706. x = FP_0_0;
  1707. }
  1708. else
  1709. {
  1710. x = vec.x;
  1711. y = vec.y;
  1712. x *= x;
  1713. y *= y;
  1714. x += y;
  1715. x.vSqrt();
  1716. }
  1717. return(x);
  1718. }
  1719. /******************************Public*Routine******************************\
  1720. * ptfxFraction(ptfx, pld, efDistance, efLineLength)
  1721. *
  1722. * Returns ptfx + pld->vecLine * (efDistance / efLineLength).
  1723. *
  1724. * History:
  1725. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1726. * Wrote it.
  1727. \**************************************************************************/
  1728. POINTFIX ptfxFraction(
  1729. POINTFIX ptfx,
  1730. PLINEDATA pld,
  1731. EFLOAT& efDistance,
  1732. EFLOAT& efLineLength)
  1733. {
  1734. ASSERTGDI(efDistance <= efLineLength, "More than line length");
  1735. ASSERTGDI(efDistance >= FP_0_0, "Negative distance");
  1736. POINTFIX ptfxResult;
  1737. if (efLineLength.bIsZero())
  1738. return(ptfx);
  1739. if (!pld->bNormalizedComputed())
  1740. {
  1741. pld->ptflNormalized.x = (LONG) pld->vecLine.x;
  1742. pld->ptflNormalized.y = (LONG) pld->vecLine.y;
  1743. // Assume a floating point multiply is twice as fast as a divide:
  1744. EFLOAT efFactor;
  1745. efFactor = FP_1_0;
  1746. efFactor /= efLineLength;
  1747. pld->ptflNormalized.x *= efFactor;
  1748. pld->ptflNormalized.y *= efFactor;
  1749. pld->vSetNormalizedComputed();
  1750. }
  1751. POINTFL ptfl;
  1752. ptfl = pld->ptflNormalized;
  1753. ptfl.x *= efDistance;
  1754. ptfl.y *= efDistance;
  1755. LONG x;
  1756. LONG y;
  1757. // bEfToL has to take a LONG argument (can't be a FIX cast to a LONG):
  1758. BOOL bRet1 = ptfl.x.bEfToL(x);
  1759. BOOL bRet2 = ptfl.y.bEfToL(y);
  1760. ASSERTGDI(bRet1 && bRet2, "Unexpected overflow");
  1761. ptfxResult = ptfx;
  1762. ptfxResult.x += (FIX) x;
  1763. ptfxResult.y += (FIX) y;
  1764. return(ptfxResult);
  1765. }
  1766. /******************************Public*Routine******************************\
  1767. * STYLER::vNextStyleEvent()
  1768. *
  1769. * Gets next style event. Could be a join, end-cap, start-cap, etc.
  1770. *
  1771. * History:
  1772. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  1773. * Wrote it.
  1774. \**************************************************************************/
  1775. VOID STYLER::vNextStyleEvent()
  1776. {
  1777. if (!bDoingStyles())
  1778. vNextEvent();
  1779. else
  1780. {
  1781. if (bStyleNext())
  1782. {
  1783. if (we == WE_STOPDASH)
  1784. {
  1785. // Handle gaps:
  1786. // This following test includes equality in order to eat zero
  1787. // length lines when on a zero length style:
  1788. while (efStyleLength >= efRemainingLength)
  1789. {
  1790. efStyleLength -= efRemainingLength;
  1791. vNextEvent();
  1792. if (we != WE_JOIN && we != WE_BEZIERJOIN)
  1793. {
  1794. // We've come to the end of the figure, and it's covered
  1795. // by a gap. Since we've effectively already put an end-cap
  1796. // on the figure (we're in a gap, after all), we can eat
  1797. // an end-cap message.
  1798. ASSERTGDI(we == WE_CLOSEFIGURE || we == WE_ENDFIGURE,
  1799. "Unexpected event");
  1800. if (we == WE_ENDFIGURE)
  1801. {
  1802. // We need the first point in the figure:
  1803. vNextEvent();
  1804. ASSERTGDI(we == WE_FINISHFIGURE, "Expected finish fig");
  1805. }
  1806. vStyleNext(FALSE);
  1807. we = WE_FINISHFIGURE;
  1808. return;
  1809. }
  1810. efDoneLength = FP_0_0;
  1811. efLineLength = efWorldLength(pldOut->vecLine);
  1812. efRemainingLength = efLineLength;
  1813. ptfxLineStart = ptfxThis;
  1814. }
  1815. efRemainingLength -= efStyleLength;
  1816. efDoneLength += efStyleLength;
  1817. ptfxThis = ptfxFraction(ptfxLineStart, pldOut, efDoneLength, efLineLength);
  1818. efStyleLength = efNextStyleLength();
  1819. we = WE_STARTDASH;
  1820. return;
  1821. }
  1822. else
  1823. {
  1824. // Handle a dash:
  1825. // If you do a Rectangle(100, 100, 200, 200) with flat caps
  1826. // and a world transform set, with a style array of
  1827. // {100, 30}, I don't want it to wrap around a corner --
  1828. // i.e., both perps for the start and end of the first dash
  1829. // should be calculated from the vector (-100, 0), and NOT
  1830. // have the start calculated from (-100, 0) and the end
  1831. // from (0, 100)..
  1832. if (efStyleLength > efRemainingLength)
  1833. {
  1834. efStyleLength -= efRemainingLength;
  1835. }
  1836. else
  1837. {
  1838. efRemainingLength -= efStyleLength;
  1839. efDoneLength += efStyleLength;
  1840. ptfxThis = ptfxFraction(ptfxLineStart, pldOut, efDoneLength, efLineLength);
  1841. pldIn = pldOut;
  1842. efStyleLength = efNextStyleLength();
  1843. we = WE_STOPDASH;
  1844. return;
  1845. }
  1846. }
  1847. }
  1848. // Okay, we're done with the current line (we've completely styled it),
  1849. // so get the next one from the path:
  1850. vNextEvent();
  1851. switch(we)
  1852. {
  1853. case WE_STARTFIGURE:
  1854. vResetStyles();
  1855. efStyleLength = efNextStyleLength();
  1856. efDoneLength = FP_0_0;
  1857. efLineLength = efWorldLength(pldOut->vecLine);
  1858. efRemainingLength = efLineLength;
  1859. ptfxLineStart = ptfxThis;
  1860. vStyleNext(TRUE);
  1861. break;
  1862. case WE_JOIN:
  1863. case WE_BEZIERJOIN:
  1864. efDoneLength = FP_0_0;
  1865. efLineLength = efWorldLength(pldOut->vecLine);
  1866. efRemainingLength = efLineLength;
  1867. ptfxLineStart = ptfxThis;
  1868. vStyleNext(TRUE);
  1869. break;
  1870. default:
  1871. vStyleNext(FALSE);
  1872. }
  1873. }
  1874. }
  1875. /******************************Public*Routine******************************\
  1876. * WIDENER::vVecDrawCompute(ld)
  1877. *
  1878. * Computes the draw vector for 'ld'.
  1879. *
  1880. * History:
  1881. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  1882. * Wrote it.
  1883. \**************************************************************************/
  1884. VOID WIDENER::vVecDrawCompute(LINEDATA& ld)
  1885. {
  1886. #ifdef DEBUG_WIDE
  1887. ASSERTGDI(ld.vecTangent.x != 0 || ld.vecTangent.y != 0,
  1888. "Can't compute draw vertex for zero vector");
  1889. #endif
  1890. wpen.vDetermineDrawVertex(ld.vecTangent, ld);
  1891. if (!ld.bInvert())
  1892. ld.vecDraw = *ld.pptfx;
  1893. else
  1894. {
  1895. ld.vecDraw.x = - ld.pptfx->x;
  1896. ld.vecDraw.y = - ld.pptfx->y;
  1897. }
  1898. ld.vSetVecDrawComputed();
  1899. vVecSymmetricRound(&ld.vecDraw);
  1900. }
  1901. /******************************Public*Routine******************************\
  1902. * WIDENER::vVecPerpCompute(ld)
  1903. *
  1904. * Computes the perp vector for 'ld'.
  1905. *
  1906. * History:
  1907. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  1908. * Wrote it.
  1909. \**************************************************************************/
  1910. VOID WIDENER::vVecPerpCompute(LINEDATA& ld)
  1911. {
  1912. ASSERTGDI(ld.vecTangent.x != 0 || ld.vecTangent.y != 0,
  1913. "Can't compute perp for zero vector");
  1914. if (!ld.bVecDrawComputed())
  1915. vVecDrawCompute(ld);
  1916. ASSERTGDI(ld.fxCrossStart >= 0 && ld.fxCrossEnd >= 0, "-ve cross");
  1917. LONGLONG aa = ld.fxCrossStart;
  1918. LONGLONG bb = ld.fxCrossEnd;
  1919. EVECTORFX vec;
  1920. if (aa <= bb)
  1921. {
  1922. vec.x = ld.pptfx->x - (ld.pptfx - 1)->x;
  1923. vec.y = ld.pptfx->y - (ld.pptfx - 1)->y;
  1924. }
  1925. else
  1926. {
  1927. vec.x = (ld.pptfx + 1)->x - ld.pptfx->x;
  1928. vec.y = (ld.pptfx + 1)->y - ld.pptfx->y;
  1929. }
  1930. POINTFIX ptfxMid;
  1931. ptfxMid.x = ld.pptfx->x - (vec.x >> 1);
  1932. ptfxMid.y = ld.pptfx->y - (vec.y >> 1);
  1933. LONGLONG x;
  1934. LONGLONG y;
  1935. LONGLONG fxDivisor = aa + bb;
  1936. x = aa * ABS(vec.x);
  1937. y = aa * ABS(vec.y);
  1938. // This is actually only an unsigned divide:
  1939. ULONG ulXRemainder;
  1940. ULONG ulYRemainder;
  1941. if (fxDivisor != 0)
  1942. {
  1943. if (fxDivisor < ULONG_MAX)
  1944. {
  1945. VDIV(x, (ULONG) fxDivisor, &ulXRemainder);
  1946. VDIV(y, (ULONG) fxDivisor, &ulYRemainder);
  1947. }
  1948. else
  1949. {
  1950. // When the numbers are this big, phooie on the rounding:
  1951. ulXRemainder = 0;
  1952. x /= fxDivisor;
  1953. ulYRemainder = 0;
  1954. y /= fxDivisor;
  1955. }
  1956. }
  1957. else
  1958. {
  1959. ulXRemainder = 0;
  1960. ulYRemainder = 0;
  1961. }
  1962. // Now adjust the signs:
  1963. FIX xPrime = (LONG) x;
  1964. FIX yPrime = (LONG) y;
  1965. // This is a safe conversion because fxDivisor is always positive:
  1966. ULONG ulHalfDivisor = (ULONG) fxDivisor;
  1967. ulHalfDivisor >>= 1;
  1968. // Round:
  1969. if (ulXRemainder >= ulHalfDivisor)
  1970. xPrime++;
  1971. if (ulYRemainder >= ulHalfDivisor)
  1972. yPrime++;
  1973. // Give it the correct sign:
  1974. if (vec.x < 0)
  1975. xPrime = -xPrime;
  1976. if (vec.y < 0)
  1977. yPrime = -yPrime;
  1978. ld.vecPerp.x = xPrime + ptfxMid.x;
  1979. ld.vecPerp.y = yPrime + ptfxMid.y;
  1980. if (ld.bInvert())
  1981. {
  1982. ld.vecPerp.x = -ld.vecPerp.x;
  1983. ld.vecPerp.y = -ld.vecPerp.y;
  1984. }
  1985. ld.vSetVecPerpComputed();
  1986. // Rounds to half integer:
  1987. vVecRound(&ld.vecPerp);
  1988. }
  1989. /******************************Public*Routine******************************\
  1990. * WIDENER::vVecSquareCompute(ld)
  1991. *
  1992. * Computes the square cap vector for 'ld'.
  1993. *
  1994. * History:
  1995. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  1996. * Wrote it.
  1997. \**************************************************************************/
  1998. VOID WIDENER::vVecSquareCompute(LINEDATA& ld)
  1999. {
  2000. ASSERTGDI(ld.vecTangent.x != 0 || ld.vecTangent.y != 0,
  2001. "Can't compute square vector for zero vector");
  2002. EFLOAT efAlpha;
  2003. EFLOAT efLength;
  2004. efAlpha = efHalfWidth;
  2005. efLength = efWorldLength(ld.vecTangent);
  2006. if (!efLength.bIsZero())
  2007. efAlpha /= efLength;
  2008. else
  2009. efAlpha = FP_0_0;
  2010. EFLOATEXT x(ld.vecTangent.x);
  2011. EFLOATEXT y(ld.vecTangent.y);
  2012. x *= efAlpha;
  2013. y *= efAlpha;
  2014. // We can assert that these conversions won't fail because we already
  2015. // did bounds checking on the entire path.
  2016. BOOL bRet1 = x.bEfToL(ld.vecSquare.x);
  2017. BOOL bRet2 = y.bEfToL(ld.vecSquare.y);
  2018. ASSERTGDI(bRet1 && bRet2, "Square vector out of bounds");
  2019. ld.vSetVecSquareComputed();
  2020. }
  2021. /******************************Public*Routine******************************\
  2022. * bIs31Bits(fx)
  2023. *
  2024. * Returns TRUE if 'fx' is a 31-bit signed number (i.e., the upper 2 bits
  2025. * are the same).
  2026. *
  2027. * History:
  2028. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2029. * Wrote it.
  2030. \**************************************************************************/
  2031. INLINE BOOL bIs31Bits(FIX fx)
  2032. {
  2033. return(fx < ((LONG) LONG_MAX >> 1) && fx > ((LONG) LONG_MIN >> 1));
  2034. }
  2035. /******************************Public*Routine******************************\
  2036. * bComputeIntersect(pvecA, pvecB, pvecC, pvecD, pvecIntersect)
  2037. *
  2038. * Computes the intersection of the rays defined by the given line segments.
  2039. * Returns FALSE if the computation overflows or if the intersection is
  2040. * outside the 31 bit space.
  2041. *
  2042. * History:
  2043. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2044. * Wrote it.
  2045. \**************************************************************************/
  2046. BOOL bComputeIntersect(
  2047. PEVECTORFX pvecA, // Start point of first line segment
  2048. PEVECTORFX pvecB, // Direction of first line segment
  2049. PEVECTORFX pvecC, // Start point of second line segment
  2050. PEVECTORFX pvecD, // Direction of second line segment
  2051. PEVECTORFX pvecIntersect) // Resulting point of intersection
  2052. {
  2053. //
  2054. // The intersection is computed by:
  2055. //
  2056. // (Cx - Ax)(-Dy) + (Cy - Ay)(Dx)
  2057. // lambda = ------------------------------
  2058. // (Bx)(-Dy) + (By)(Dx)
  2059. //
  2060. // intersect = A + lambda * B
  2061. //
  2062. EFLOAT efTerm1;
  2063. EFLOAT efTerm2;
  2064. EFLOAT efNum;
  2065. EFLOAT efDenom;
  2066. // (Cx - Ax)(-Dy)
  2067. efNum = (pvecC->x - pvecA->x);
  2068. efTerm2 = -pvecD->y;
  2069. efNum *= efTerm2;
  2070. // (Cy - Ay)(Dx)
  2071. efTerm1 = (pvecC->y - pvecA->y);
  2072. efTerm2 = pvecD->x;
  2073. efTerm1 *= efTerm2;
  2074. efNum += efTerm1;
  2075. // (Bx)(-Dy)
  2076. efDenom = pvecB->x;
  2077. efTerm2 = -pvecD->y;
  2078. efDenom *= efTerm2;
  2079. // (By)(Dx)
  2080. efTerm1 = pvecB->y;
  2081. efTerm2 = pvecD->x;
  2082. efTerm1 *= efTerm2;
  2083. efDenom += efTerm1;
  2084. if (efDenom.bIsZero())
  2085. return(FALSE);
  2086. // lambda
  2087. efNum /= efDenom;
  2088. // lambda * B
  2089. EVECTORFX lambdaB;
  2090. efTerm1 = pvecB->x;
  2091. efTerm2 = pvecB->y;
  2092. efTerm1 *= efNum;
  2093. efTerm2 *= efNum;
  2094. if (!efTerm1.bEfToL(lambdaB.x) || !efTerm2.bEfToL(lambdaB.y) ||
  2095. !bIs31Bits(lambdaB.x) || !bIs31Bits(lambdaB.y))
  2096. {
  2097. return(FALSE);
  2098. }
  2099. // A + lambda * B
  2100. pvecIntersect->x = pvecA->x + lambdaB.x;
  2101. pvecIntersect->y = pvecA->y + lambdaB.y;
  2102. // Check for overflow:
  2103. if (!bIs31Bits(pvecIntersect->x) || !bIs31Bits(pvecIntersect->y))
  2104. {
  2105. WARNING("Miter would fall outside device space\n");
  2106. return(FALSE);
  2107. }
  2108. return(TRUE);
  2109. }
  2110. /******************************Public*Routine******************************\
  2111. * WIDENER::bMiterInLimit(vec)
  2112. *
  2113. * Determines if the resulting vector from the middle of the wideline to
  2114. * the outer corner would be within the specified miter limit.
  2115. *
  2116. * History:
  2117. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2118. * Wrote it.
  2119. \**************************************************************************/
  2120. BOOL WIDENER::bMiterInLimit(EVECTORFX vec)
  2121. {
  2122. // Note we change our local copy of 'vec':
  2123. BOOL bRet = exoDeviceToWorld.bXform(&vec, (PVECTORL) &vec, 1);
  2124. ASSERTGDI(bRet, "XForm failed");
  2125. // Now compute the square of the Cartesian length:
  2126. EFLOAT x;
  2127. EFLOAT y;
  2128. x = vec.x;
  2129. y = vec.y;
  2130. x *= x;
  2131. y *= y;
  2132. x += y;
  2133. return(x <= efHalfWidthMiterLimitSquared);
  2134. }
  2135. /******************************Public*Routine******************************\
  2136. * WIDENER:vAddRoundJoin(bBezierJoin)
  2137. *
  2138. * Adds a join.
  2139. *
  2140. * History:
  2141. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2142. * Wrote it.
  2143. \**************************************************************************/
  2144. VOID WIDENER::vAddRoundJoin(BOOL bBezierJoin)
  2145. {
  2146. BOOL bTurnLeft = bTurnLeftRandom(&pldIn->vecTangent, &pldOut->vecTangent);
  2147. BOOL bInPerp = FALSE;
  2148. BOOL bOutPerp = FALSE;
  2149. if (!bAllRound())
  2150. {
  2151. if (!bBezierJoin)
  2152. {
  2153. bInPerp = TRUE;
  2154. bOutPerp = TRUE;
  2155. }
  2156. // else
  2157. // {
  2158. // if (pldIn->bSamePenSection(ldStartTangent)
  2159. // || pldIn->bSamePenSection(ldEndTangent))
  2160. // bInPerp = TRUE;
  2161. //
  2162. // if (pldOut->bSamePenSection(ldStartTangent)
  2163. // || pldOut->bSamePenSection(ldEndTangent))
  2164. // bOutPerp = TRUE;
  2165. // }
  2166. }
  2167. EVECTORFX vecIn = bInPerp ? vecInPerp() : vecInDraw();
  2168. EVECTORFX vecOut = bOutPerp ? vecOutPerp() : vecOutDraw();
  2169. vAddRight(vecIn);
  2170. vAddLeft(vecIn);
  2171. if (vecIn != vecOut)
  2172. {
  2173. COUNT cpt;
  2174. if (bTurnLeft)
  2175. {
  2176. cpt = wpen.cptAddRound(*this, *pldIn, *pldOut, bTurnLeft, bInPerp, bOutPerp);
  2177. vAddLeft();
  2178. if (!bAllRound())
  2179. // if ((cpt > 0 || bInPerp || bOutPerp) && bBezierJoin) // hmmm and not all round?
  2180. {
  2181. vAddLeft(vecOut);
  2182. if (cpt > 0)
  2183. wpen.cptAddRound(*this, *pldOut, *pldIn, !bTurnLeft, bOutPerp, bInPerp);
  2184. vAddLeft(vecIn);
  2185. vAddLeft();
  2186. }
  2187. }
  2188. else
  2189. {
  2190. cpt = wpen.cptAddRound(*this, *pldIn, *pldOut, bTurnLeft, bInPerp, bOutPerp);
  2191. vAddRight();
  2192. if (!bAllRound())
  2193. // if ((cpt > 0 || bInPerp || bOutPerp) && bBezierJoin) // hmmm and not all round?
  2194. {
  2195. vAddRight(vecOut);
  2196. if (cpt > 0)
  2197. wpen.cptAddRound(*this, *pldOut, *pldIn, !bTurnLeft, bOutPerp, bInPerp);
  2198. vAddRight(vecIn);
  2199. vAddRight();
  2200. }
  2201. }
  2202. vAddRight(vecOut);
  2203. vAddLeft(vecOut);
  2204. }
  2205. }
  2206. /******************************Public*Routine******************************\
  2207. * WIDENER:vAddJoin(bBezierJoin)
  2208. *
  2209. * Adds a join.
  2210. *
  2211. * History:
  2212. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2213. * Wrote it.
  2214. \**************************************************************************/
  2215. VOID WIDENER::vAddJoin(BOOL bBezierJoin)
  2216. {
  2217. BOOL bTurnLeft;
  2218. if (iJoin == JOIN_ROUND || bBezierJoin)
  2219. vAddRoundJoin(bBezierJoin);
  2220. else if (iJoin == JOIN_BEVEL)
  2221. {
  2222. bTurnLeft = bTurnLeftRandom(&pldIn->vecTangent, &pldOut->vecTangent);
  2223. EVECTORFX vecIn = vecInPerp();
  2224. EVECTORFX vecOut = vecOutPerp();
  2225. vAddLeft(vecIn);
  2226. vAddRight(vecIn);
  2227. if (vecIn != vecOut)
  2228. {
  2229. if (bTurnLeft)
  2230. vAddLeft();
  2231. else
  2232. vAddRight();
  2233. vAddRight(vecOut);
  2234. vAddLeft(vecOut);
  2235. }
  2236. }
  2237. else
  2238. {
  2239. ASSERTGDI(iJoin == JOIN_MITER, "Unexpected join type");
  2240. bTurnLeft = bTurnLeftRandom(&pldIn->vecTangent, &pldOut->vecTangent);
  2241. // Use the 'perpendicular' vector rather than the 'draw' vector here for
  2242. // computing the intersection point. This is done to be consistent:
  2243. // if the same path is redrawn but with a different miter limit, we will
  2244. // still light the same pels except on the joins that change between
  2245. // mitered and beveled.
  2246. EVECTORFX vecIn = vecInPerp();
  2247. EVECTORFX vecOut = vecOutPerp();
  2248. vAddLeft(vecIn);
  2249. vAddRight(vecIn);
  2250. if (vecIn != vecOut)
  2251. {
  2252. EVECTORFX vecIntersect;
  2253. if (bComputeIntersect(&vecIn, &pldIn->vecLine,
  2254. &vecOut, &pldOut->vecLine,
  2255. &vecIntersect))
  2256. if (bMiterInLimit(vecIntersect))
  2257. {
  2258. if (bTurnLeft)
  2259. vAddRight(vecIntersect);
  2260. else
  2261. vAddLeft(vecIntersect);
  2262. }
  2263. if (bTurnLeft)
  2264. vAddLeft();
  2265. else
  2266. vAddRight();
  2267. vAddLeft(vecOut);
  2268. vAddRight(vecOut);
  2269. }
  2270. }
  2271. }
  2272. /******************************Public*Routine******************************\
  2273. * WIDENER::vAddEndCap()
  2274. *
  2275. * Adds an end-cap.
  2276. *
  2277. * History:
  2278. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2279. * Wrote it.
  2280. \**************************************************************************/
  2281. VOID WIDENER::vAddEndCap()
  2282. {
  2283. switch(iEndCap)
  2284. {
  2285. case ENDCAP_SQUARE:
  2286. EVECTORFX vecRight;
  2287. EVECTORFX vecLeft;
  2288. vecRight = vecInSquare();
  2289. vecLeft = vecRight;
  2290. vecRight += vecInPerp();
  2291. vecLeft -= vecInPerp();
  2292. vAddRight(vecRight);
  2293. vAddRight(vecLeft);
  2294. break;
  2295. case ENDCAP_BUTT:
  2296. EVECTORFX vecInP;
  2297. vecInP = vecInPerp();
  2298. vAddRight(vecInP, FALSE);
  2299. vAddRight(vecInP, TRUE);
  2300. break;
  2301. case ENDCAP_ROUND:
  2302. EVECTORFX vec;
  2303. if (bAllRound())
  2304. vec = vecInDraw();
  2305. else
  2306. vec = vecInPerp();
  2307. vAddRight(vec, FALSE);
  2308. wpen.vAddRoundEndCap(*this, *pldIn, FALSE, bAllRound());
  2309. vAddRight(vec, TRUE);
  2310. break;
  2311. default:
  2312. RIP("Unknown cap type");
  2313. }
  2314. }
  2315. /******************************Public*Routine******************************\
  2316. * WIDENER::vAddStartCap()
  2317. *
  2318. * Adds a start cap.
  2319. *
  2320. * History:
  2321. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2322. * Wrote it.
  2323. \**************************************************************************/
  2324. VOID WIDENER::vAddStartCap()
  2325. {
  2326. switch(iEndCap)
  2327. {
  2328. case ENDCAP_SQUARE:
  2329. {
  2330. EVECTORFX vecRight = vecOutSquare();
  2331. EVECTORFX vecLeft;
  2332. vecRight.x = -vecRight.x;
  2333. vecRight.y = -vecRight.y;
  2334. vecLeft = vecRight;
  2335. vecRight += vecOutPerp();
  2336. vecLeft -= vecOutPerp();
  2337. vAddRight(vecLeft);
  2338. vAddRight(vecRight);
  2339. }
  2340. break;
  2341. case ENDCAP_BUTT:
  2342. EVECTORFX vecOutP;
  2343. vecOutP = vecOutPerp();
  2344. vAddRight(vecOutP, TRUE);
  2345. vAddRight(vecOutP, FALSE);
  2346. break;
  2347. case ENDCAP_ROUND:
  2348. {
  2349. EVECTORFX vec;
  2350. if (bAllRound())
  2351. vec = vecOutDraw();
  2352. else
  2353. vec = vecOutPerp();
  2354. vAddRight(vec, TRUE);
  2355. wpen.vAddRoundEndCap(*this, *pldOut, TRUE, bAllRound());
  2356. vAddRight(vec, FALSE);
  2357. }
  2358. break;
  2359. default:
  2360. RIP("Unknown cap type");
  2361. }
  2362. }
  2363. /******************************Public*Routine******************************\
  2364. * BOOL WIDENER::bWiden()
  2365. *
  2366. * Widens the path.
  2367. *
  2368. * History:
  2369. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2370. * Wrote it.
  2371. \**************************************************************************/
  2372. BOOL WIDENER::bWiden()
  2373. {
  2374. while(TRUE)
  2375. {
  2376. vNextStyleEvent();
  2377. switch (we)
  2378. {
  2379. case WE_ZEROFIGURE:
  2380. // If the entire figure is comprised of one point, output the
  2381. // pen circle at that point if we have round caps (we can't handle
  2382. // square caps because we have no idea of the intended direction):
  2383. if (iEndCap == ENDCAP_ROUND)
  2384. {
  2385. if (!wpathRight.bBeginFigure())
  2386. return(FALSE);
  2387. vAddStartCap();
  2388. vAddEndCap();
  2389. // Finish up:
  2390. wpathRight.vEndFigure();
  2391. wpathRight.vCloseFigure();
  2392. }
  2393. break;
  2394. case WE_STARTFIGURE:
  2395. if (!wpathLeft.bBeginFigure() || !wpathRight.bBeginFigure())
  2396. return(FALSE);
  2397. vFigureStyled(FALSE);
  2398. wpathRight.vMarkFigureStart();
  2399. break;
  2400. case WE_JOIN:
  2401. vAddJoin(FALSE);
  2402. break;
  2403. case WE_BEZIERJOIN:
  2404. vAddJoin(TRUE);
  2405. break;
  2406. case WE_STARTDASH:
  2407. if (!wpathLeft.bBeginFigure() || !wpathRight.bBeginFigure())
  2408. return(FALSE);
  2409. vAddStartCap();
  2410. break;
  2411. case WE_ENDFIGURE:
  2412. case WE_STOPDASH:
  2413. vAddEndCap();
  2414. // Finish up:
  2415. wpathLeft.vEndFigure();
  2416. if (!bValid())
  2417. return(FALSE);
  2418. wpathRight.vReverseConcatenate(wpathLeft);
  2419. wpathRight.vEndFigure();
  2420. wpathRight.vCloseFigure();
  2421. vFigureStyled(TRUE); // We hit a style dash
  2422. break;
  2423. case WE_FINISHFIGURE:
  2424. // Prepend the start cap to the beginning of the figure:
  2425. if (!wpathRight.bBeginFigure())
  2426. return(FALSE);
  2427. vAddStartCap();
  2428. wpathRight.vEndFigure();
  2429. wpathRight.vPrependBeforeFigure();
  2430. break;
  2431. case WE_CLOSEFIGURE:
  2432. vAddJoin(FALSE);
  2433. wpathLeft.vEndFigure();
  2434. if (!bFigureStyled())
  2435. {
  2436. wpathRight.vEndFigure();
  2437. wpathRight.vCloseFigure();
  2438. if (!wpathRight.bBeginFigure())
  2439. return(FALSE);
  2440. wpathRight.vReverseConcatenate(wpathLeft);
  2441. wpathRight.vEndFigure();
  2442. wpathRight.vCloseFigure();
  2443. }
  2444. else
  2445. {
  2446. wpathRight.vEndFigure();
  2447. if (!wpathRight.bBeginFigure())
  2448. return(FALSE);
  2449. wpathRight.vReverseConcatenate(wpathLeft);
  2450. wpathRight.vEndFigure();
  2451. wpathRight.vPrependBeforeSubpath();
  2452. wpathRight.vPrependBeforeFigure();
  2453. wpathRight.vCloseFigure();
  2454. }
  2455. break;
  2456. case WE_DONEPATH:
  2457. return(bValid());
  2458. default:
  2459. RIP("Unknown widen event");
  2460. }
  2461. }
  2462. RIP("Shouldn't get here");
  2463. return(FALSE);
  2464. }
  2465. /******************************Public*Routine******************************\
  2466. * EPATHOBJ::vBecome(wpath)
  2467. *
  2468. * Free any data in our path, copy all the data from 'epo', and delete
  2469. * 'epo'.
  2470. *
  2471. * Our path can be a PATHMEMOBJ or PATHSTACKOBJ.
  2472. *
  2473. * History:
  2474. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2475. * Wrote it.
  2476. \**************************************************************************/
  2477. INLINE VOID EPATHOBJ::vBecome(WIDEPATHOBJ& wpath)
  2478. {
  2479. ASSERTGDI(!(wpath.ppath->flType & PATHTYPE_STACK), "Can't do stacks");
  2480. // First, free all the path blocks in our destination path:
  2481. vFreeBlocks();
  2482. // Now copy all the important stuff about the path from 'wpath', including
  2483. // the pointers to all its path data:
  2484. cCurves = wpath.cCurves;
  2485. ppath->ppachain = wpath.ppath->ppachain;
  2486. ppath->pprfirst = wpath.ppath->pprfirst;
  2487. ppath->pprlast = wpath.ppath->pprlast;
  2488. // The flags, ercfxBoundBox and ptfxCurrent fields should be the same for
  2489. // the widened result as they were for the spine, so don't copy them.
  2490. ppath->flags |= (PD_BEGINSUBPATH | PD_ENDSUBPATH);
  2491. fl &= ~(PO_BEZIERS | PO_ELLIPSE);
  2492. // Now delete the wpath path object:
  2493. wpath.ppath->ppachain = (PATHALLOC*) NULL;
  2494. wpath.vDelete();
  2495. }
  2496. /******************************Public*Routine******************************\
  2497. * WIDENER::vMakeItWide(epo)
  2498. *
  2499. * Replaces the path 'epo' with its widened result.
  2500. *
  2501. * History:
  2502. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2503. * Wrote it.
  2504. \**************************************************************************/
  2505. INLINE VOID WIDENER::vMakeItWide(EPATHOBJ& epo)
  2506. {
  2507. epo.vBecome(wpathRight);
  2508. // Recompute the total number of lines in the path:
  2509. epo.cCurves = epo.cTotalCurves();
  2510. }
  2511. /******************************Public*Routine******************************\
  2512. * WIDENER::WIDENER(epo, exoWtoD, pla)
  2513. *
  2514. * Constructor for the widener.
  2515. *
  2516. * Coming in to here, we expect bounds checking to be already done via
  2517. * bComputeWidenedBounds(), so that we don't have to worry about over-
  2518. * flowing outside the 31 bit device space.
  2519. *
  2520. * History:
  2521. * 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
  2522. * Wrote it.
  2523. \**************************************************************************/
  2524. WIDENER::WIDENER(
  2525. EPATHOBJ& epo,
  2526. EXFORMOBJ& exoWtoD,
  2527. PLINEATTRS pla)
  2528. : STYLER(epo, pla)
  2529. {
  2530. ASSERTGDI(pla->fl & LA_GEOMETRIC, "Not a geometric line");
  2531. ASSERTGDI(epo.bValid(), "Invalid path");
  2532. ASSERTGDI(exoWtoD.bValid(), "Invalid xform");
  2533. // Bail out here if we failed to allocate any objects. The caller must also
  2534. // call WIDENER::bValid() immediately after invoking the WIDENER constructor:
  2535. if (!bValid())
  2536. return;
  2537. iEndCap = pla->iEndCap;
  2538. iJoin = pla->iJoin;
  2539. // Set 'AllRound' flag if we don't have to ever worry about perpendiculars
  2540. // looking funny:
  2541. vAllRound((iJoin == JOIN_ROUND) &&
  2542. (iEndCap == ENDCAP_ROUND || iEndCap == ENDCAP_SQUARE));
  2543. EFLOATEXT efWidth(pla->elWidth.e);
  2544. LONG lWidth;
  2545. BOOL bRet;
  2546. bRet = efWidth.bEfToL(lWidth);
  2547. ASSERTGDI(bRet, "Unexpected width overflow");
  2548. efHalfWidth = efWidth;
  2549. efHalfWidth.vDivBy2();
  2550. // Set up for miter limit:
  2551. if (iJoin == JOIN_MITER)
  2552. {
  2553. // Compute (PenWidth * MiterLimit / 2) ^ 2:
  2554. efHalfWidthMiterLimitSquared = pla->eMiterLimit;
  2555. efHalfWidthMiterLimitSquared *= efHalfWidth;
  2556. // *= operator probably can't handle dest and src being the same:
  2557. EFLOAT efTemp = efHalfWidthMiterLimitSquared;
  2558. efHalfWidthMiterLimitSquared *= efTemp;
  2559. }
  2560. // Current transform has to be invertible for doing styling or mitering
  2561. // (miter limit is specified in world space). Square caps too.
  2562. if (pla->pstyle != (PFLOAT_LONG) NULL ||
  2563. iEndCap == ENDCAP_SQUARE || iJoin == JOIN_MITER)
  2564. {
  2565. // Compute inverse of the current transform:
  2566. if (exoDeviceToWorld.bInverse(exoWtoD))
  2567. {
  2568. // Ensure that every vector between every point in the path
  2569. // could be transformed to world space:
  2570. EVECTORFX avec[2];
  2571. avec[0].x = epo.rcfxBoundBox().xRight - epo.rcfxBoundBox().xLeft;
  2572. avec[0].y = epo.rcfxBoundBox().yBottom - epo.rcfxBoundBox().yTop;
  2573. avec[1].x = -avec[0].x;
  2574. avec[1].y = avec[0].y;
  2575. if (!exoDeviceToWorld.bXform(avec, (PVECTORL) avec, 2))
  2576. vSetError();
  2577. }
  2578. else
  2579. {
  2580. SAVE_ERROR_CODE(ERROR_ARITHMETIC_OVERFLOW);
  2581. vSetError();
  2582. }
  2583. }
  2584. if (!bValid())
  2585. return;
  2586. // Do most of the work:
  2587. if (!wpen.bPolygonizePen(exoWtoD, lWidth) ||
  2588. !bWiden())
  2589. {
  2590. vSetError();
  2591. }
  2592. }
  2593. /******************************Public*Routine******************************\
  2594. * EPATHOBJ::bWiden(epoSpine, exo, pla)
  2595. *
  2596. * Widens the specified path, overwriting the results of '*this'.
  2597. *
  2598. * History:
  2599. * 3-Feb-1992 -by- J. Andrew Goossen [andrewgo]
  2600. * Wrote it.
  2601. \**************************************************************************/
  2602. BOOL EPATHOBJ::bWiden(EPATHOBJ& epoSpine, XFORMOBJ* pxo, LINEATTRS* pla)
  2603. {
  2604. WIDENER wid(epoSpine, *((EXFORMOBJ*) pxo), pla);
  2605. BOOL bValid = wid.bValid();
  2606. if (bValid)
  2607. wid.vMakeItWide(*this);
  2608. return(bValid);
  2609. }
  2610. /******************************Public*Routine******************************\
  2611. * EPATHOBJ::bComputeWidenedBounds(exo, pla)
  2612. *
  2613. * Adjusts the bounds of the path to allow for joins for geometric
  2614. * wide-lines. We do this by transforming the bound box of the pen
  2615. * to device space and taking the corners as the extrema of the joins.
  2616. *
  2617. * For Miter joins, this is an even rougher guess since we assume we'll
  2618. * have the longest possible join allowed by the Miter limit.
  2619. *
  2620. * The values returned are the upper bounds if the path is to be widened
  2621. * (which is fine for pointer exclusion).
  2622. *
  2623. * FALSE is returned if any part of the widened result might fall outside
  2624. * the 31 bit device space.
  2625. *
  2626. * History:
  2627. * 3-Feb-1992 -by- J. Andrew Goossen [andrewgo]
  2628. * Wrote it.
  2629. \**************************************************************************/
  2630. BOOL EPATHOBJ::bComputeWidenedBounds(
  2631. EPATHOBJ& epoSpine,
  2632. XFORMOBJ *pexo,
  2633. LINEATTRS *pla)
  2634. {
  2635. ASSERTGDI(epoSpine.bValid(), "Not valid path");
  2636. ASSERTGDI(bValid(), "Not valid path");
  2637. ASSERTGDI(pla->fl & LA_GEOMETRIC, "Not geometric line");
  2638. EFLOATEXT efWidth(pla->elWidth.e);
  2639. LONG lWidth;
  2640. BOOL b = efWidth.bEfToL(lWidth);
  2641. ASSERTGDI(b, "Width too large");
  2642. ASSERTGDI(lWidth >= 0, "Negative pen radius");
  2643. VECTORL avecl[2];
  2644. // We use the diameter and not the radius of the pen here because halving
  2645. // it in world space as an integer could possibly lose a lot of precision,
  2646. // and it's faster to call 'bXform' with an integer vector than an EFLOAT
  2647. // one:
  2648. avecl[0].x = lWidth;
  2649. avecl[0].y = lWidth;
  2650. avecl[1].x = lWidth;
  2651. avecl[1].y = -lWidth;
  2652. if (!((EXFORMOBJ*) pexo)->bXform(avecl, (PVECTORFX) avecl, 2))
  2653. return(FALSE);
  2654. // Chop in half to get the maximums of the pen (we were using the diameter
  2655. // instead of the radius, remember?). Add a pixel of slop too (remembering
  2656. // that there is rounding error, and vAddNice can increase the dimensions
  2657. // of the pen slightly):
  2658. LONG xAdjust = (MAX(ABS(avecl[0].x), ABS(avecl[1].x)) >> 1) + LTOFX(1);
  2659. LONG yAdjust = (MAX(ABS(avecl[0].y), ABS(avecl[1].y)) >> 1) + LTOFX(1);
  2660. // Account for square caps by multiplying by a coarse approximation to the
  2661. // square root of 2:
  2662. if (pla->iEndCap == ENDCAP_SQUARE)
  2663. {
  2664. xAdjust += (xAdjust >> 1);
  2665. yAdjust += (yAdjust >> 1);
  2666. // Watch for overflow:
  2667. if (!bIs31Bits(xAdjust) || !bIs31Bits(yAdjust))
  2668. return(FALSE);
  2669. }
  2670. // We know here that xAdjust and yAdjust have at most 31 bits significance.
  2671. // (Ok, so maybe we overflowed by adding 1).
  2672. if (pla->iJoin == JOIN_MITER)
  2673. {
  2674. EFLOATEXT efMiterLimit(pla->eMiterLimit);
  2675. ASSERTGDI(FP_1_0 <= efMiterLimit, "Miter limit less than one");
  2676. EFLOATEXT ef(xAdjust);
  2677. ef *= efMiterLimit;
  2678. if (!ef.bEfToL(xAdjust))
  2679. return(FALSE);
  2680. ef = yAdjust;
  2681. ef *= efMiterLimit;
  2682. if (!ef.bEfToL(yAdjust))
  2683. return(FALSE);
  2684. if (!bIs31Bits(xAdjust) || !bIs31Bits(yAdjust))
  2685. return(FALSE);
  2686. }
  2687. PATH *ppNew = ppath;
  2688. PATH *ppOld = epoSpine.ppath;
  2689. LONG xLeft = ppOld->rcfxBoundBox.xLeft;
  2690. LONG xRight = ppOld->rcfxBoundBox.xRight;
  2691. LONG yTop = ppOld->rcfxBoundBox.yTop;
  2692. LONG yBottom = ppOld->rcfxBoundBox.yBottom;
  2693. // Our widen code expects the coordinates to fix in a 27.4 space in order
  2694. // not to overflow (as guaranteed by the DDI spec, but not guaranteed by
  2695. // the code):
  2696. if (!bIs31Bits(xLeft) || !bIs31Bits(xRight) ||
  2697. !bIs31Bits(yTop) || !bIs31Bits(yBottom))
  2698. {
  2699. return(FALSE);
  2700. }
  2701. // Copy the bounds from the 'old' spine to the 'new' widened result,
  2702. // accounting for the resulting increase in size:
  2703. xLeft -= xAdjust;
  2704. xRight += xAdjust;
  2705. yTop -= yAdjust;
  2706. yBottom += yAdjust;
  2707. // Make sure we the widened result doesn't fall outside the 31 bit space:
  2708. if (!bIs31Bits(xLeft) || !bIs31Bits(xRight) ||
  2709. !bIs31Bits(yTop) || !bIs31Bits(yBottom))
  2710. {
  2711. return(FALSE);
  2712. }
  2713. ppNew->rcfxBoundBox.xLeft = xLeft;
  2714. ppNew->rcfxBoundBox.xRight = xRight;
  2715. ppNew->rcfxBoundBox.yTop = yTop;
  2716. ppNew->rcfxBoundBox.yBottom = yBottom;
  2717. return(TRUE);
  2718. }
  2719. /******************************Public*Routine******************************\
  2720. * EPATHOBJ::vReComputeBounds()
  2721. *
  2722. * Updates the bounding rectangle, based on reading all the points in
  2723. * the path.
  2724. *
  2725. * History:
  2726. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  2727. * Wrote it.
  2728. \**************************************************************************/
  2729. VOID EPATHOBJ::vReComputeBounds()
  2730. {
  2731. ASSERTGDI(bValid(), "ReComputeWidenedBounds() in invalid path");
  2732. PPATHREC ppr;
  2733. if (ppath != (PPATH) NULL)
  2734. {
  2735. if (ppath->pprfirst != (PPATHREC) NULL)
  2736. {
  2737. ASSERTGDI(ppath->pprfirst->count > 0, "Shouldn't have empty pathrec");
  2738. ppath->rcfxBoundBox.xLeft = ppath->pprfirst->aptfx->x;
  2739. ppath->rcfxBoundBox.yTop = ppath->pprfirst->aptfx->y;
  2740. ppath->rcfxBoundBox.xRight = ppath->rcfxBoundBox.xLeft;
  2741. ppath->rcfxBoundBox.yBottom = ppath->rcfxBoundBox.yTop;
  2742. for (ppr = ppath->pprfirst; ppr != (PPATHREC) NULL; ppr = ppr->pprnext)
  2743. {
  2744. PPOINTFIX pptfx = &ppr->aptfx[0];
  2745. PPOINTFIX pptfxEnd = &ppr->aptfx[ppr->count];
  2746. while (pptfx < pptfxEnd)
  2747. ((ERECTFX*) &ppath->rcfxBoundBox)->vInclude(*pptfx++);
  2748. }
  2749. }
  2750. else
  2751. {
  2752. ppath->rcfxBoundBox.xLeft = 0;
  2753. ppath->rcfxBoundBox.yTop = 0;
  2754. ppath->rcfxBoundBox.xRight = 0;
  2755. ppath->rcfxBoundBox.yBottom = 0;
  2756. }
  2757. }
  2758. }
  2759. /******************************Public*Routine******************************\
  2760. * EPATHOBJ::vWidenSetupForFrameRgn(dco, cxPen, cyPen, pexo, pla)
  2761. *
  2762. * Initializes a LINEATTRS and EXFORMOBJ to be passed to bWiden to widen
  2763. * the path appropriately. Used only for FrameRgn.
  2764. *
  2765. * The FrameRgn pen is elliptical in world space, whereas the Widen code
  2766. * expects it to be circular. So we simply change the transform here.
  2767. *
  2768. * The FrameRgn ellipse may also have zero-length axis (unlike normal
  2769. * widened paths), so a flag must be set for that.
  2770. *
  2771. * History:
  2772. * 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
  2773. * Wrote it.
  2774. \**************************************************************************/
  2775. VOID EPATHOBJ::vWidenSetupForFrameRgn(
  2776. XDCOBJ& dco, // Used to get current world transform
  2777. LONG cxPen, // x dimension of pen in world space
  2778. LONG cyPen, // y dimension of pen
  2779. EXFORMOBJ* pexo, // Must be initialized via 'vInit(&mx, DONT_COMPUTE_FLAGS)'
  2780. LINEATTRS* pla)
  2781. {
  2782. ASSERTGDI(pexo->bValid(), "Xform must be initialized");
  2783. // FrameRgn can have zero-width or zero-height brush dimensions:
  2784. pla->fl = LA_GEOMETRIC | LA_ALLOW_ZERO_DIMENSIONS;
  2785. pla->iJoin = JOIN_MITER;
  2786. pla->iEndCap = ENDCAP_ROUND;
  2787. pla->eMiterLimit = dco.pdc->l_eMiterLimit();
  2788. pla->pstyle = (FLOAT_LONG*) NULL;
  2789. pla->cstyle = 0;
  2790. // We need to double the pen dimensions because half of the pen
  2791. // will draw outside the region, and will be clipped away:
  2792. ASSERTGDI(cxPen >= 0 && cyPen >= 0, "Illegal pen dimension");
  2793. cxPen *= 2;
  2794. cyPen *= 2;
  2795. // Make our calculations a little more numerically stable:
  2796. BOOL bAdjustX = FALSE;
  2797. if (cxPen < cyPen)
  2798. {
  2799. register LONG lTmp;
  2800. SWAPL(cxPen, cyPen, lTmp);
  2801. bAdjustX = !bAdjustX;
  2802. }
  2803. // Figure out the y-value normalizing factor that will make a square
  2804. // of dimensions [cxPen, cxPen] become a rectangle [cxPen, cyPen]:
  2805. ASSERTGDI(cxPen >= cyPen, "");
  2806. EFLOATEXT efX(cxPen);
  2807. efX.vEfToF(pla->elWidth.e);
  2808. EFLOATEXT efNormalize(cyPen);
  2809. if (!efX.bIsZero())
  2810. efNormalize /= efX;
  2811. // Now copy the world transform and modify it:
  2812. pexo->vSet(&dco.pdc->mxWorldToDevice());
  2813. if (bAdjustX)
  2814. {
  2815. pexo->efM11() *= efNormalize;
  2816. pexo->efM12() *= efNormalize;
  2817. }
  2818. else
  2819. {
  2820. pexo->efM21() *= efNormalize;
  2821. pexo->efM22() *= efNormalize;
  2822. }
  2823. pexo->vComputeAccelFlags();
  2824. }