blob: 23698d324541a70a916ccc166361540141ce8787 [file] [log] [blame]
Alexandre Julliard44ed71f1997-12-21 19:17:50 +00001/*
2 * Graphics paths (BeginPath, EndPath etc.)
3 *
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00004 * Copyright 1997, 1998 Martin Boehme
Alexandre Julliard44ed71f1997-12-21 19:17:50 +00005 */
6
7#include <assert.h>
8#include <malloc.h>
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00009#include <math.h>
Alexandre Julliard44ed71f1997-12-21 19:17:50 +000010
11#include "windows.h"
12#include "winerror.h"
13
14#include "dc.h"
15#include "debug.h"
16#include "path.h"
17
18/* Notes on the implementation
19 *
20 * The implementation is based on dynamically resizable arrays of points and
21 * flags. I dithered for a bit before deciding on this implementation, and
22 * I had even done a bit of work on a linked list version before switching
23 * to arrays. It's a bit of a tradeoff. When you use linked lists, the
24 * implementation of FlattenPath is easier, because you can rip the
25 * PT_BEZIERTO entries out of the middle of the list and link the
26 * corresponding PT_LINETO entries in. However, when you use arrays,
27 * PathToRegion becomes easier, since you can essentially just pass your array
28 * of points to CreatePolyPolygonRgn. Also, if I'd used linked lists, I would
29 * have had the extra effort of creating a chunk-based allocation scheme
30 * in order to use memory effectively. That's why I finally decided to use
31 * arrays. Note by the way that the array based implementation has the same
32 * linear time complexity that linked lists would have since the arrays grow
33 * exponentially.
34 *
35 * The points are stored in the path in device coordinates. This is
36 * consistent with the way Windows does things (for instance, see the Win32
37 * SDK documentation for GetPath).
38 *
39 * The word "stroke" appears in several places (e.g. in the flag
40 * GdiPath.newStroke). A stroke consists of a PT_MOVETO followed by one or
41 * more PT_LINETOs or PT_BEZIERTOs, up to, but not including, the next
42 * PT_MOVETO. Note that this is not the same as the definition of a figure;
43 * a figure can contain several strokes.
44 *
45 * I modified the drawing functions (MoveTo, LineTo etc.) to test whether
46 * the path is open and to call the corresponding function in path.c if this
47 * is the case. A more elegant approach would be to modify the function
48 * pointers in the DC_FUNCTIONS structure; however, this would be a lot more
49 * complex. Also, the performance degradation caused by my approach in the
50 * case where no path is open is so small that it cannot be measured.
51 *
52 * Martin Boehme
53 */
54
55/* FIXME: A lot of stuff isn't implemented yet. There is much more to come. */
56
57#define NUM_ENTRIES_INITIAL 16 /* Initial size of points / flags arrays */
58#define GROW_FACTOR_NUMER 2 /* Numerator of grow factor for the array */
59#define GROW_FACTOR_DENOM 1 /* Denominator of grow factor */
60
61
62static BOOL32 PATH_PathToRegion(const GdiPath *pPath, INT32 nPolyFillMode,
63 HRGN32 *pHrgn);
64static void PATH_EmptyPath(GdiPath *pPath);
Alexandre Julliarddadf78f1998-05-17 17:13:43 +000065static BOOL32 PATH_AddEntry(GdiPath *pPath, const POINT32 *pPoint,
66 BYTE flags);
Alexandre Julliard44ed71f1997-12-21 19:17:50 +000067static BOOL32 PATH_ReserveEntries(GdiPath *pPath, INT32 numEntries);
68static BOOL32 PATH_GetPathFromHDC(HDC32 hdc, GdiPath **ppPath);
Alexandre Julliarddadf78f1998-05-17 17:13:43 +000069static BOOL32 PATH_DoArcPart(GdiPath *pPath, FLOAT_POINT corners[],
Alexandre Julliard60ce85c1998-02-01 18:33:27 +000070 double angleStart, double angleEnd, BOOL32 addMoveTo);
Alexandre Julliarddadf78f1998-05-17 17:13:43 +000071static void PATH_ScaleNormalizedPoint(FLOAT_POINT corners[], double x,
72 double y, POINT32 *pPoint);
73static void PATH_NormalizePoint(FLOAT_POINT corners[], const FLOAT_POINT
74 *pPoint, double *pX, double *pY);
Alexandre Julliard60ce85c1998-02-01 18:33:27 +000075
76
77/***********************************************************************
78 * BeginPath16 (GDI.512)
79 */
80BOOL16 WINAPI BeginPath16(HDC16 hdc)
81{
82 return (BOOL16)BeginPath32((HDC32)hdc);
83}
84
Alexandre Julliard44ed71f1997-12-21 19:17:50 +000085
86/***********************************************************************
87 * BeginPath32 (GDI32.9)
88 */
89BOOL32 WINAPI BeginPath32(HDC32 hdc)
90{
91 GdiPath *pPath;
92
93 /* Get pointer to path */
94 if(!PATH_GetPathFromHDC(hdc, &pPath))
95 {
96 SetLastError(ERROR_INVALID_HANDLE);
97 return FALSE;
98 }
99
100 /* If path is already open, do nothing */
101 if(pPath->state==PATH_Open)
102 return TRUE;
103
104 /* Make sure that path is empty */
105 PATH_EmptyPath(pPath);
106
107 /* Initialize variables for new path */
108 pPath->newStroke=TRUE;
109 pPath->state=PATH_Open;
110
111 return TRUE;
112}
113
114
115/***********************************************************************
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000116 * EndPath16 (GDI.514)
117 */
118BOOL16 WINAPI EndPath16(HDC16 hdc)
119{
120 return (BOOL16)EndPath32((HDC32)hdc);
121}
122
123
124/***********************************************************************
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000125 * EndPath32 (GDI32.78)
126 */
127BOOL32 WINAPI EndPath32(HDC32 hdc)
128{
129 GdiPath *pPath;
130
131 /* Get pointer to path */
132 if(!PATH_GetPathFromHDC(hdc, &pPath))
133 {
134 SetLastError(ERROR_INVALID_HANDLE);
135 return FALSE;
136 }
137
138 /* Check that path is currently being constructed */
139 if(pPath->state!=PATH_Open)
140 {
141 SetLastError(ERROR_CAN_NOT_COMPLETE);
142 return FALSE;
143 }
144
145 /* Set flag to indicate that path is finished */
146 pPath->state=PATH_Closed;
147
148 return TRUE;
149}
150
151
152/***********************************************************************
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000153 * AbortPath16 (GDI.511)
154 */
155BOOL16 WINAPI AbortPath16(HDC16 hdc)
156{
157 return (BOOL16)AbortPath32((HDC32)hdc);
158}
159
160
Alexandre Julliard46ea8b31998-05-03 19:01:20 +0000161/******************************************************************************
162 * AbortPath32 [GDI32.1]
163 * Closes and discards paths from device context
164 *
165 * NOTES
166 * Check that SetLastError is being called correctly
167 *
168 * PARAMS
169 * hdc [I] Handle to device context
170 *
171 * RETURNS STD
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000172 */
Alexandre Julliard46ea8b31998-05-03 19:01:20 +0000173BOOL32 WINAPI AbortPath32( HDC32 hdc )
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000174{
175 GdiPath *pPath;
176
177 /* Get pointer to path */
178 if(!PATH_GetPathFromHDC(hdc, &pPath))
179 {
180 SetLastError(ERROR_INVALID_PARAMETER);
181 return FALSE;
182 }
183
184 /* Remove all entries from the path */
185 PATH_EmptyPath(pPath);
186
187 return TRUE;
188}
189
190
191/***********************************************************************
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000192 * CloseFigure16 (GDI.513)
193 */
194BOOL16 WINAPI CloseFigure16(HDC16 hdc)
195{
196 return (BOOL16)CloseFigure32((HDC32)hdc);
197}
198
199
200/***********************************************************************
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000201 * CloseFigure32 (GDI32.16)
Alexandre Julliard829fe321998-07-26 14:27:39 +0000202 *
203 * FIXME: Check that SetLastError is being called correctly
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000204 */
205BOOL32 WINAPI CloseFigure32(HDC32 hdc)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000206{
207 GdiPath *pPath;
208
209 /* Get pointer to path */
210 if(!PATH_GetPathFromHDC(hdc, &pPath))
211 {
212 SetLastError(ERROR_INVALID_PARAMETER);
213 return FALSE;
214 }
215
216 /* Check that path is open */
217 if(pPath->state!=PATH_Open)
218 {
219 SetLastError(ERROR_CAN_NOT_COMPLETE);
220 return FALSE;
221 }
222
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000223 /* FIXME: Shouldn't we draw a line to the beginning of the figure? */
224
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000225 /* Set PT_CLOSEFIGURE on the last entry and start a new stroke */
226 if(pPath->numEntriesUsed)
227 {
228 pPath->pFlags[pPath->numEntriesUsed-1]|=PT_CLOSEFIGURE;
229 pPath->newStroke=TRUE;
230 }
231
232 return TRUE;
233}
234
235
236/***********************************************************************
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000237 * GetPath16 (GDI.517)
238 */
239INT16 WINAPI GetPath16(HDC16 hdc, LPPOINT16 pPoints, LPBYTE pTypes,
240 INT16 nSize)
241{
Alexandre Julliardd30dfd21998-09-27 18:28:36 +0000242 FIXME(gdi, "(%d,%p,%p): stub\n",hdc,pPoints,pTypes);
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000243
244 return 0;
245}
246
247
248/***********************************************************************
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000249 * GetPath32 (GDI32.210)
250 */
251INT32 WINAPI GetPath32(HDC32 hdc, LPPOINT32 pPoints, LPBYTE pTypes,
252 INT32 nSize)
253{
254 GdiPath *pPath;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000255
256 /* Get pointer to path */
257 if(!PATH_GetPathFromHDC(hdc, &pPath))
258 {
259 SetLastError(ERROR_INVALID_PARAMETER);
260 return -1;
261 }
262
263 /* Check that path is closed */
264 if(pPath->state!=PATH_Closed)
265 {
266 SetLastError(ERROR_CAN_NOT_COMPLETE);
267 return -1;
268 }
269
270 if(nSize==0)
271 return pPath->numEntriesUsed;
272 else if(nSize<pPath->numEntriesUsed)
273 {
274 SetLastError(ERROR_INVALID_PARAMETER);
275 return -1;
276 }
277 else
278 {
279 memcpy(pPoints, pPath->pPoints, sizeof(POINT32)*pPath->numEntriesUsed);
280 memcpy(pTypes, pPath->pFlags, sizeof(BYTE)*pPath->numEntriesUsed);
281
282 /* Convert the points to logical coordinates */
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000283 if(!DPtoLP32(hdc, pPoints, pPath->numEntriesUsed))
284 {
285 /* FIXME: Is this the correct value? */
286 SetLastError(ERROR_CAN_NOT_COMPLETE);
287 return -1;
288 }
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000289
290 return pPath->numEntriesUsed;
291 }
292}
293
294
295/***********************************************************************
296 * PathToRegion32 (GDI32.261)
Alexandre Julliard829fe321998-07-26 14:27:39 +0000297 *
298 * FIXME
299 * Check that SetLastError is being called correctly
300 *
301 * The documentation does not state this explicitly, but a test under Windows
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000302 * shows that the region which is returned should be in device coordinates.
303 */
Alexandre Julliard829fe321998-07-26 14:27:39 +0000304HRGN32 WINAPI PathToRegion32(HDC32 hdc)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000305{
306 GdiPath *pPath;
307 HRGN32 hrgnRval;
308
309 /* Get pointer to path */
310 if(!PATH_GetPathFromHDC(hdc, &pPath))
311 {
312 SetLastError(ERROR_INVALID_PARAMETER);
313 return 0;
314 }
315
316 /* Check that path is closed */
317 if(pPath->state!=PATH_Closed)
318 {
319 SetLastError(ERROR_CAN_NOT_COMPLETE);
320 return 0;
321 }
322
323 /* FIXME: Should we empty the path even if conversion failed? */
324 if(PATH_PathToRegion(pPath, GetPolyFillMode32(hdc), &hrgnRval))
325 PATH_EmptyPath(pPath);
326 else
327 hrgnRval=0;
328
329 return hrgnRval;
330}
331
332
333/***********************************************************************
334 * FillPath32 (GDI32.100)
Alexandre Julliard829fe321998-07-26 14:27:39 +0000335 *
336 * FIXME
337 * Check that SetLastError is being called correctly
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000338 */
339BOOL32 WINAPI FillPath32(HDC32 hdc)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000340{
341 GdiPath *pPath;
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000342 INT32 mapMode, graphicsMode;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000343 POINT32 ptViewportExt, ptViewportOrg, ptWindowExt, ptWindowOrg;
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000344 XFORM xform;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000345 HRGN32 hrgn;
346
347 /* Get pointer to path */
348 if(!PATH_GetPathFromHDC(hdc, &pPath))
349 {
350 SetLastError(ERROR_INVALID_PARAMETER);
351 return FALSE;
352 }
353
354 /* Check that path is closed */
355 if(pPath->state!=PATH_Closed)
356 {
357 SetLastError(ERROR_CAN_NOT_COMPLETE);
358 return FALSE;
359 }
360
361 /* Construct a region from the path and fill it */
362 if(PATH_PathToRegion(pPath, GetPolyFillMode32(hdc), &hrgn))
363 {
364 /* Since PaintRgn interprets the region as being in logical coordinates
365 * but the points we store for the path are already in device
366 * coordinates, we have to set the mapping mode to MM_TEXT temporarily.
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000367 * Using SaveDC to save information about the mapping mode / world
368 * transform would be easier but would require more overhead, especially
369 * now that SaveDC saves the current path.
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000370 */
371
372 /* Save the information about the old mapping mode */
373 mapMode=GetMapMode32(hdc);
374 GetViewportExtEx32(hdc, &ptViewportExt);
375 GetViewportOrgEx32(hdc, &ptViewportOrg);
376 GetWindowExtEx32(hdc, &ptWindowExt);
377 GetWindowOrgEx32(hdc, &ptWindowOrg);
378
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000379 /* Save world transform
380 * NB: The Windows documentation on world transforms would lead one to
381 * believe that this has to be done only in GM_ADVANCED; however, my
382 * tests show that resetting the graphics mode to GM_COMPATIBLE does
383 * not reset the world transform.
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000384 */
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000385 GetWorldTransform(hdc, &xform);
386
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000387 /* Set MM_TEXT */
388 SetMapMode32(hdc, MM_TEXT);
389
390 /* Paint the region */
391 PaintRgn32(hdc, hrgn);
392
393 /* Restore the old mapping mode */
394 SetMapMode32(hdc, mapMode);
395 SetViewportExtEx32(hdc, ptViewportExt.x, ptViewportExt.y, NULL);
396 SetViewportOrgEx32(hdc, ptViewportOrg.x, ptViewportOrg.y, NULL);
397 SetWindowExtEx32(hdc, ptWindowExt.x, ptWindowExt.y, NULL);
398 SetWindowOrgEx32(hdc, ptWindowOrg.x, ptWindowOrg.y, NULL);
399
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000400 /* Go to GM_ADVANCED temporarily to restore the world transform */
401 graphicsMode=GetGraphicsMode(hdc);
402 SetGraphicsMode(hdc, GM_ADVANCED);
403 SetWorldTransform(hdc, &xform);
404 SetGraphicsMode(hdc, graphicsMode);
405
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000406 /* Empty the path */
407 PATH_EmptyPath(pPath);
408 return TRUE;
409 }
410 else
411 {
412 /* FIXME: Should the path be emptied even if conversion failed? */
413 /* PATH_EmptyPath(pPath); */
414 return FALSE;
415 }
416}
417
418
419/***********************************************************************
420 * SelectClipPath32 (GDI32.296)
Alexandre Julliard829fe321998-07-26 14:27:39 +0000421 * FIXME
422 * Check that SetLastError is being called correctly
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000423 */
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000424BOOL32 WINAPI SelectClipPath32(HDC32 hdc, INT32 iMode)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000425{
426 GdiPath *pPath;
Ulrich Weigandd4663661998-10-11 18:47:02 +0000427 HRGN32 hrgnPath;
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000428 BOOL32 success;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000429
430 /* Get pointer to path */
431 if(!PATH_GetPathFromHDC(hdc, &pPath))
432 {
433 SetLastError(ERROR_INVALID_PARAMETER);
434 return FALSE;
435 }
436
437 /* Check that path is closed */
438 if(pPath->state!=PATH_Closed)
439 {
440 SetLastError(ERROR_CAN_NOT_COMPLETE);
441 return FALSE;
442 }
443
444 /* Construct a region from the path */
445 if(PATH_PathToRegion(pPath, GetPolyFillMode32(hdc), &hrgnPath))
446 {
Ulrich Weigandd4663661998-10-11 18:47:02 +0000447 success = ExtSelectClipRgn( hdc, hrgnPath, iMode ) != ERROR;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000448 DeleteObject32(hrgnPath);
449
450 /* Empty the path */
451 if(success)
452 PATH_EmptyPath(pPath);
453 /* FIXME: Should this function delete the path even if it failed? */
454
455 return success;
456 }
457 else
458 return FALSE;
459}
460
461
462/***********************************************************************
463 * Exported functions
464 */
465
466/* PATH_InitGdiPath
467 *
468 * Initializes the GdiPath structure.
469 */
470void PATH_InitGdiPath(GdiPath *pPath)
471{
472 assert(pPath!=NULL);
473
474 pPath->state=PATH_Null;
475 pPath->pPoints=NULL;
476 pPath->pFlags=NULL;
477 pPath->numEntriesUsed=0;
478 pPath->numEntriesAllocated=0;
479}
480
481/* PATH_DestroyGdiPath
482 *
483 * Destroys a GdiPath structure (frees the memory in the arrays).
484 */
485void PATH_DestroyGdiPath(GdiPath *pPath)
486{
487 assert(pPath!=NULL);
488
489 free(pPath->pPoints);
490 free(pPath->pFlags);
491}
492
493/* PATH_AssignGdiPath
494 *
495 * Copies the GdiPath structure "pPathSrc" to "pPathDest". A deep copy is
496 * performed, i.e. the contents of the pPoints and pFlags arrays are copied,
497 * not just the pointers. Since this means that the arrays in pPathDest may
498 * need to be resized, pPathDest should have been initialized using
499 * PATH_InitGdiPath (in C++, this function would be an assignment operator,
500 * not a copy constructor).
501 * Returns TRUE if successful, else FALSE.
502 */
503BOOL32 PATH_AssignGdiPath(GdiPath *pPathDest, const GdiPath *pPathSrc)
504{
505 assert(pPathDest!=NULL && pPathSrc!=NULL);
506
507 /* Make sure destination arrays are big enough */
508 if(!PATH_ReserveEntries(pPathDest, pPathSrc->numEntriesUsed))
509 return FALSE;
510
511 /* Perform the copy operation */
512 memcpy(pPathDest->pPoints, pPathSrc->pPoints,
513 sizeof(POINT32)*pPathSrc->numEntriesUsed);
514 memcpy(pPathDest->pFlags, pPathSrc->pFlags,
515 sizeof(INT32)*pPathSrc->numEntriesUsed);
516 pPathDest->state=pPathSrc->state;
517 pPathDest->numEntriesUsed=pPathSrc->numEntriesUsed;
518 pPathDest->newStroke=pPathSrc->newStroke;
519
520 return TRUE;
521}
522
523/* PATH_MoveTo
524 *
525 * Should be called when a MoveTo is performed on a DC that has an
526 * open path. This starts a new stroke. Returns TRUE if successful, else
527 * FALSE.
528 */
529BOOL32 PATH_MoveTo(HDC32 hdc)
530{
531 GdiPath *pPath;
532
533 /* Get pointer to path */
534 if(!PATH_GetPathFromHDC(hdc, &pPath))
535 return FALSE;
536
537 /* Check that path is open */
538 if(pPath->state!=PATH_Open)
539 /* FIXME: Do we have to call SetLastError? */
540 return FALSE;
541
542 /* Start a new stroke */
543 pPath->newStroke=TRUE;
544
545 return TRUE;
546}
547
548/* PATH_LineTo
549 *
550 * Should be called when a LineTo is performed on a DC that has an
551 * open path. This adds a PT_LINETO entry to the path (and possibly
552 * a PT_MOVETO entry, if this is the first LineTo in a stroke).
553 * Returns TRUE if successful, else FALSE.
554 */
555BOOL32 PATH_LineTo(HDC32 hdc, INT32 x, INT32 y)
556{
557 GdiPath *pPath;
558 POINT32 point, pointCurPos;
559
560 /* Get pointer to path */
561 if(!PATH_GetPathFromHDC(hdc, &pPath))
562 return FALSE;
563
564 /* Check that path is open */
565 if(pPath->state!=PATH_Open)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000566 return FALSE;
567
568 /* Convert point to device coordinates */
569 point.x=x;
570 point.y=y;
571 if(!LPtoDP32(hdc, &point, 1))
572 return FALSE;
573
574 /* Add a PT_MOVETO if necessary */
575 if(pPath->newStroke)
576 {
577 pPath->newStroke=FALSE;
578 if(!GetCurrentPositionEx32(hdc, &pointCurPos) ||
579 !LPtoDP32(hdc, &pointCurPos, 1))
580 return FALSE;
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000581 if(!PATH_AddEntry(pPath, &pointCurPos, PT_MOVETO))
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000582 return FALSE;
583 }
584
585 /* Add a PT_LINETO entry */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000586 return PATH_AddEntry(pPath, &point, PT_LINETO);
587}
588
589/* PATH_Rectangle
590 *
591 * Should be called when a call to Rectangle is performed on a DC that has
592 * an open path. Returns TRUE if successful, else FALSE.
593 */
594BOOL32 PATH_Rectangle(HDC32 hdc, INT32 x1, INT32 y1, INT32 x2, INT32 y2)
595{
596 GdiPath *pPath;
597 POINT32 corners[2], pointTemp;
598 INT32 temp;
599
600 /* Get pointer to path */
601 if(!PATH_GetPathFromHDC(hdc, &pPath))
602 return FALSE;
603
604 /* Check that path is open */
605 if(pPath->state!=PATH_Open)
606 return FALSE;
607
608 /* Convert points to device coordinates */
609 corners[0].x=x1;
610 corners[0].y=y1;
611 corners[1].x=x2;
612 corners[1].y=y2;
613 if(!LPtoDP32(hdc, corners, 2))
614 return FALSE;
615
616 /* Make sure first corner is top left and second corner is bottom right */
617 if(corners[0].x>corners[1].x)
618 {
619 temp=corners[0].x;
620 corners[0].x=corners[1].x;
621 corners[1].x=temp;
622 }
623 if(corners[0].y>corners[1].y)
624 {
625 temp=corners[0].y;
626 corners[0].y=corners[1].y;
627 corners[1].y=temp;
628 }
629
630 /* In GM_COMPATIBLE, don't include bottom and right edges */
631 if(GetGraphicsMode(hdc)==GM_COMPATIBLE)
632 {
633 corners[1].x--;
634 corners[1].y--;
635 }
636
637 /* Close any previous figure */
638 if(!CloseFigure32(hdc))
639 {
640 /* The CloseFigure call shouldn't have failed */
641 assert(FALSE);
642 return FALSE;
643 }
644
645 /* Add four points to the path */
646 pointTemp.x=corners[1].x;
647 pointTemp.y=corners[0].y;
648 if(!PATH_AddEntry(pPath, &pointTemp, PT_MOVETO))
649 return FALSE;
650 if(!PATH_AddEntry(pPath, corners, PT_LINETO))
651 return FALSE;
652 pointTemp.x=corners[0].x;
653 pointTemp.y=corners[1].y;
654 if(!PATH_AddEntry(pPath, &pointTemp, PT_LINETO))
655 return FALSE;
656 if(!PATH_AddEntry(pPath, corners+1, PT_LINETO))
657 return FALSE;
658
659 /* Close the rectangle figure */
660 if(!CloseFigure32(hdc))
661 {
662 /* The CloseFigure call shouldn't have failed */
663 assert(FALSE);
664 return FALSE;
665 }
666
667 return TRUE;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000668}
669
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000670/* PATH_Ellipse
671 *
672 * Should be called when a call to Ellipse is performed on a DC that has
673 * an open path. This adds four Bezier splines representing the ellipse
674 * to the path. Returns TRUE if successful, else FALSE.
675 */
676BOOL32 PATH_Ellipse(HDC32 hdc, INT32 x1, INT32 y1, INT32 x2, INT32 y2)
677{
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000678 // TODO: This should probably be revised to call PATH_AngleArc
679 // (once it exists)
680 return PATH_Arc(hdc, x1, y1, x2, y2, x1, (y1+y2)/2, x1, (y1+y2)/2);
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000681}
682
683/* PATH_Arc
684 *
685 * Should be called when a call to Arc is performed on a DC that has
686 * an open path. This adds up to five Bezier splines representing the arc
687 * to the path. Returns TRUE if successful, else FALSE.
688 */
689BOOL32 PATH_Arc(HDC32 hdc, INT32 x1, INT32 y1, INT32 x2, INT32 y2,
690 INT32 xStart, INT32 yStart, INT32 xEnd, INT32 yEnd)
691{
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000692 GdiPath *pPath;
693 DC *pDC;
694 double angleStart, angleEnd, angleStartQuadrant, angleEndQuadrant=0.0;
695 /* Initialize angleEndQuadrant to silence gcc's warning */
696 double x, y;
697 FLOAT_POINT corners[2], pointStart, pointEnd;
698 BOOL32 start, end;
699 INT32 temp;
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000700
701 /* FIXME: This function should check for all possible error returns */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000702 /* FIXME: Do we have to respect newStroke? */
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000703
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000704 /* Get pointer to DC */
705 pDC=DC_GetDCPtr(hdc);
706 if(pDC==NULL)
707 return FALSE;
708
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000709 /* Get pointer to path */
710 if(!PATH_GetPathFromHDC(hdc, &pPath))
711 return FALSE;
712
713 /* Check that path is open */
714 if(pPath->state!=PATH_Open)
715 return FALSE;
716
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000717 /* FIXME: Do we have to close the current figure? */
718
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000719 /* Check for zero height / width */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000720 /* FIXME: Only in GM_COMPATIBLE? */
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000721 if(x1==x2 || y1==y2)
722 return TRUE;
723
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000724 /* Convert points to device coordinates */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000725 corners[0].x=(FLOAT)x1;
726 corners[0].y=(FLOAT)y1;
727 corners[1].x=(FLOAT)x2;
728 corners[1].y=(FLOAT)y2;
729 pointStart.x=(FLOAT)xStart;
730 pointStart.y=(FLOAT)yStart;
731 pointEnd.x=(FLOAT)xEnd;
732 pointEnd.y=(FLOAT)yEnd;
733 INTERNAL_LPTODP_FLOAT(pDC, corners);
734 INTERNAL_LPTODP_FLOAT(pDC, corners+1);
735 INTERNAL_LPTODP_FLOAT(pDC, &pointStart);
736 INTERNAL_LPTODP_FLOAT(pDC, &pointEnd);
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000737
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000738 /* Make sure first corner is top left and second corner is bottom right */
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000739 if(corners[0].x>corners[1].x)
740 {
741 temp=corners[0].x;
742 corners[0].x=corners[1].x;
743 corners[1].x=temp;
744 }
745 if(corners[0].y>corners[1].y)
746 {
747 temp=corners[0].y;
748 corners[0].y=corners[1].y;
749 corners[1].y=temp;
750 }
751
752 /* Compute start and end angle */
753 PATH_NormalizePoint(corners, &pointStart, &x, &y);
754 angleStart=atan2(y, x);
755 PATH_NormalizePoint(corners, &pointEnd, &x, &y);
756 angleEnd=atan2(y, x);
757
758 /* Make sure the end angle is "on the right side" of the start angle */
759 if(GetArcDirection32(hdc)==AD_CLOCKWISE)
760 {
761 if(angleEnd<=angleStart)
762 {
763 angleEnd+=2*M_PI;
764 assert(angleEnd>=angleStart);
765 }
766 }
767 else
768 {
769 if(angleEnd>=angleStart)
770 {
771 angleEnd-=2*M_PI;
772 assert(angleEnd<=angleStart);
773 }
774 }
775
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000776 /* In GM_COMPATIBLE, don't include bottom and right edges */
777 if(GetGraphicsMode(hdc)==GM_COMPATIBLE)
778 {
779 corners[1].x--;
780 corners[1].y--;
781 }
782
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000783 /* Add the arc to the path with one Bezier spline per quadrant that the
784 * arc spans */
785 start=TRUE;
786 end=FALSE;
787 do
788 {
789 /* Determine the start and end angles for this quadrant */
790 if(start)
791 {
792 angleStartQuadrant=angleStart;
793 if(GetArcDirection32(hdc)==AD_CLOCKWISE)
794 angleEndQuadrant=(floor(angleStart/M_PI_2)+1.0)*M_PI_2;
795 else
796 angleEndQuadrant=(ceil(angleStart/M_PI_2)-1.0)*M_PI_2;
797 }
798 else
799 {
800 angleStartQuadrant=angleEndQuadrant;
801 if(GetArcDirection32(hdc)==AD_CLOCKWISE)
802 angleEndQuadrant+=M_PI_2;
803 else
804 angleEndQuadrant-=M_PI_2;
805 }
806
807 /* Have we reached the last part of the arc? */
808 if((GetArcDirection32(hdc)==AD_CLOCKWISE &&
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000809 angleEnd<angleEndQuadrant) ||
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000810 (GetArcDirection32(hdc)==AD_COUNTERCLOCKWISE &&
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000811 angleEnd>angleEndQuadrant))
Alexandre Julliard60ce85c1998-02-01 18:33:27 +0000812 {
813 /* Adjust the end angle for this quadrant */
814 angleEndQuadrant=angleEnd;
815 end=TRUE;
816 }
817
818 /* Add the Bezier spline to the path */
819 PATH_DoArcPart(pPath, corners, angleStartQuadrant, angleEndQuadrant,
820 start);
821 start=FALSE;
822 } while(!end);
823
824 return TRUE;
825}
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000826
827/***********************************************************************
828 * Internal functions
829 */
830
831/* PATH_PathToRegion
832 *
833 * Creates a region from the specified path using the specified polygon
834 * filling mode. The path is left unchanged. A handle to the region that
835 * was created is stored in *pHrgn. If successful, TRUE is returned; if an
836 * error occurs, SetLastError is called with the appropriate value and
837 * FALSE is returned.
838 */
839static BOOL32 PATH_PathToRegion(const GdiPath *pPath, INT32 nPolyFillMode,
840 HRGN32 *pHrgn)
841{
842 int numStrokes, iStroke, i;
843 INT32 *pNumPointsInStroke;
844 HRGN32 hrgn;
845
846 assert(pPath!=NULL);
847 assert(pHrgn!=NULL);
848
849 /* FIXME: What happens when number of points is zero? */
850
851 /* First pass: Find out how many strokes there are in the path */
852 /* FIXME: We could eliminate this with some bookkeeping in GdiPath */
853 numStrokes=0;
854 for(i=0; i<pPath->numEntriesUsed; i++)
855 if((pPath->pFlags[i] & ~PT_CLOSEFIGURE) == PT_MOVETO)
856 numStrokes++;
857
858 /* Allocate memory for number-of-points-in-stroke array */
859 pNumPointsInStroke=(int *)malloc(sizeof(int)*numStrokes);
860 if(!pNumPointsInStroke)
861 {
862 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
863 return FALSE;
864 }
865
866 /* Second pass: remember number of points in each polygon */
867 iStroke=-1; /* Will get incremented to 0 at beginning of first stroke */
868 for(i=0; i<pPath->numEntriesUsed; i++)
869 {
870 /* Is this the beginning of a new stroke? */
871 if((pPath->pFlags[i] & ~PT_CLOSEFIGURE) == PT_MOVETO)
872 {
873 iStroke++;
874 pNumPointsInStroke[iStroke]=0;
875 }
876
877 pNumPointsInStroke[iStroke]++;
878 }
879
880 /* Create a region from the strokes */
881 hrgn=CreatePolyPolygonRgn32(pPath->pPoints, pNumPointsInStroke,
882 numStrokes, nPolyFillMode);
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000883 if(hrgn==(HRGN32)0)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000884 {
885 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
886 return FALSE;
887 }
888
889 /* Free memory for number-of-points-in-stroke array */
890 free(pNumPointsInStroke);
891
892 /* Success! */
893 *pHrgn=hrgn;
894 return TRUE;
895}
896
897/* PATH_EmptyPath
898 *
899 * Removes all entries from the path and sets the path state to PATH_Null.
900 */
901static void PATH_EmptyPath(GdiPath *pPath)
902{
903 assert(pPath!=NULL);
904
905 pPath->state=PATH_Null;
906 pPath->numEntriesUsed=0;
907}
908
909/* PATH_AddEntry
910 *
911 * Adds an entry to the path. For "flags", pass either PT_MOVETO, PT_LINETO
912 * or PT_BEZIERTO, optionally ORed with PT_CLOSEFIGURE. Returns TRUE if
913 * successful, FALSE otherwise (e.g. if not enough memory was available).
914 */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000915BOOL32 PATH_AddEntry(GdiPath *pPath, const POINT32 *pPoint, BYTE flags)
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000916{
917 assert(pPath!=NULL);
918
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000919 /* FIXME: If newStroke is true, perhaps we want to check that we're
920 * getting a PT_MOVETO
921 */
922
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000923 /* Check that path is open */
924 if(pPath->state!=PATH_Open)
925 return FALSE;
926
927 /* Reserve enough memory for an extra path entry */
928 if(!PATH_ReserveEntries(pPath, pPath->numEntriesUsed+1))
929 return FALSE;
930
931 /* Store information in path entry */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000932 pPath->pPoints[pPath->numEntriesUsed]=*pPoint;
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000933 pPath->pFlags[pPath->numEntriesUsed]=flags;
934
Alexandre Julliarddadf78f1998-05-17 17:13:43 +0000935 /* If this is PT_CLOSEFIGURE, we have to start a new stroke next time */
936 if((flags & PT_CLOSEFIGURE) == PT_CLOSEFIGURE)
937 pPath->newStroke=TRUE;
938
Alexandre Julliard44ed71f1997-12-21 19:17:50 +0000939 /* Increment entry count */
940 pPath->numEntriesUsed++;
941
942 return TRUE;
943}
944
945/* PATH_ReserveEntries
946 *
947 * Ensures that at least "numEntries" entries (for points and flags) have
948 * been allocated; allocates larger arrays and copies the existing entries
949 * to those arrays, if necessary. Returns TRUE if successful, else FALSE.
950 */
951static BOOL32 PATH_ReserveEntries(GdiPath *pPath, INT32 numEntries)
952{
953 INT32 numEntriesToAllocate;
954 POINT32 *pPointsNew;
955 BYTE *pFlagsNew;
956
957 assert(pPath!=NULL);
958 assert(numEntries>=0);
959
960 /* Do we have to allocate more memory? */
961 if(numEntries > pPath->numEntriesAllocated)
962 {
963 /* Find number of entries to allocate. We let the size of the array
964 * grow exponentially, since that will guarantee linear time
965 * complexity. */
966 if(pPath->numEntriesAllocated)
967 {
968 numEntriesToAllocate=pPath->numEntriesAllocated;
969 while(numEntriesToAllocate<numEntries)
970 numEntriesToAllocate=numEntriesToAllocate*GROW_FACTOR_NUMER/
971 GROW_FACTOR_DENOM;
972 }
973 else
974 numEntriesToAllocate=NUM_ENTRIES_INITIAL;
975
976 /* Allocate new arrays */
977 pPointsNew=(POINT32 *)malloc(numEntriesToAllocate * sizeof(POINT32));
978 if(!pPointsNew)
979 return FALSE;
980 pFlagsNew=(BYTE *)malloc(numEntriesToAllocate * sizeof(BYTE));
981 if(!pFlagsNew)
982 {
983 free(pPointsNew);
984 return FALSE;
985 }
986
987 /* Copy old arrays to new arrays and discard old arrays */
988 if(pPath->pPoints)
989 {
990 assert(pPath->pFlags);
991
992 memcpy(pPointsNew, pPath->pPoints,
993 sizeof(POINT32)*pPath->numEntriesUsed);
994 memcpy(pFlagsNew, pPath->pFlags,
995 sizeof(BYTE)*pPath->numEntriesUsed);
996
997 free(pPath->pPoints);
998 free(pPath->pFlags);
999 }
1000 pPath->pPoints=pPointsNew;
1001 pPath->pFlags=pFlagsNew;
1002 pPath->numEntriesAllocated=numEntriesToAllocate;
1003 }
1004
1005 return TRUE;
1006}
1007
1008/* PATH_GetPathFromHDC
1009 *
1010 * Retrieves a pointer to the GdiPath structure contained in an HDC and
1011 * places it in *ppPath. TRUE is returned if successful, FALSE otherwise.
1012 */
1013static BOOL32 PATH_GetPathFromHDC(HDC32 hdc, GdiPath **ppPath)
1014{
1015 DC *pDC;
1016
1017 pDC=DC_GetDCPtr(hdc);
1018 if(pDC)
1019 {
1020 *ppPath=&pDC->w.path;
1021 return TRUE;
1022 }
1023 else
1024 return FALSE;
1025}
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001026
1027/* PATH_DoArcPart
1028 *
1029 * Creates a Bezier spline that corresponds to part of an arc and appends the
1030 * corresponding points to the path. The start and end angles are passed in
1031 * "angleStart" and "angleEnd"; these angles should span a quarter circle
1032 * at most. If "addMoveTo" is true, a PT_MOVETO entry for the first control
1033 * point is added to the path; otherwise, it is assumed that the current
1034 * position is equal to the first control point.
1035 */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001036static BOOL32 PATH_DoArcPart(GdiPath *pPath, FLOAT_POINT corners[],
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001037 double angleStart, double angleEnd, BOOL32 addMoveTo)
1038{
1039 double halfAngle, a;
1040 double xNorm[4], yNorm[4];
1041 POINT32 point;
1042 int i;
1043
1044 assert(fabs(angleEnd-angleStart)<=M_PI_2);
1045
1046 /* FIXME: Is there an easier way of computing this? */
1047
1048 /* Compute control points */
1049 halfAngle=(angleEnd-angleStart)/2.0;
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001050 if(fabs(halfAngle)>1e-8)
1051 {
1052 a=4.0/3.0*(1-cos(halfAngle))/sin(halfAngle);
1053 xNorm[0]=cos(angleStart);
1054 yNorm[0]=sin(angleStart);
1055 xNorm[1]=xNorm[0] - a*yNorm[0];
1056 yNorm[1]=yNorm[0] + a*xNorm[0];
1057 xNorm[3]=cos(angleEnd);
1058 yNorm[3]=sin(angleEnd);
1059 xNorm[2]=xNorm[3] + a*yNorm[3];
1060 yNorm[2]=yNorm[3] - a*xNorm[3];
1061 }
1062 else
1063 for(i=0; i<4; i++)
1064 {
1065 xNorm[i]=cos(angleStart);
1066 yNorm[i]=sin(angleStart);
1067 }
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001068
1069 /* Add starting point to path if desired */
1070 if(addMoveTo)
1071 {
1072 PATH_ScaleNormalizedPoint(corners, xNorm[0], yNorm[0], &point);
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001073 if(!PATH_AddEntry(pPath, &point, PT_MOVETO))
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001074 return FALSE;
1075 }
1076
1077 /* Add remaining control points */
1078 for(i=1; i<4; i++)
1079 {
1080 PATH_ScaleNormalizedPoint(corners, xNorm[i], yNorm[i], &point);
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001081 if(!PATH_AddEntry(pPath, &point, PT_BEZIERTO))
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001082 return FALSE;
1083 }
1084
1085 return TRUE;
1086}
1087
1088/* PATH_ScaleNormalizedPoint
1089 *
1090 * Scales a normalized point (x, y) with respect to the box whose corners are
1091 * passed in "corners". The point is stored in "*pPoint". The normalized
1092 * coordinates (-1.0, -1.0) correspond to corners[0], the coordinates
1093 * (1.0, 1.0) correspond to corners[1].
1094 */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001095static void PATH_ScaleNormalizedPoint(FLOAT_POINT corners[], double x,
1096 double y, POINT32 *pPoint)
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001097{
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001098 pPoint->x=GDI_ROUND( (double)corners[0].x +
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001099 (double)(corners[1].x-corners[0].x)*0.5*(x+1.0) );
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001100 pPoint->y=GDI_ROUND( (double)corners[0].y +
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001101 (double)(corners[1].y-corners[0].y)*0.5*(y+1.0) );
1102}
1103
1104/* PATH_NormalizePoint
1105 *
1106 * Normalizes a point with respect to the box whose corners are passed in
1107 * "corners". The normalized coordinates are stored in "*pX" and "*pY".
1108 */
Alexandre Julliarddadf78f1998-05-17 17:13:43 +00001109static void PATH_NormalizePoint(FLOAT_POINT corners[],
1110 const FLOAT_POINT *pPoint,
Alexandre Julliard60ce85c1998-02-01 18:33:27 +00001111 double *pX, double *pY)
1112{
1113 *pX=(double)(pPoint->x-corners[0].x)/(double)(corners[1].x-corners[0].x) *
1114 2.0 - 1.0;
1115 *pY=(double)(pPoint->y-corners[0].y)/(double)(corners[1].y-corners[0].y) *
1116 2.0 - 1.0;
1117}