1 /* Window-specific OpenGL functions implementation.
2 *
3 * Copyright (c) 1999 Lionel Ulmer
4 * Copyright (c) 2005 Raphael Junqueira
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include "opengl32.h"
22
23 #include <math.h>
24
25 WINE_DEFAULT_DEBUG_CHANNEL(wgl);
26
27 /***********************************************************************
28 * wglUseFontBitmaps_common
29 */
wglUseFontBitmaps_common(HDC hdc,DWORD first,DWORD count,DWORD listBase,BOOL unicode)30 static BOOL wglUseFontBitmaps_common( HDC hdc, DWORD first, DWORD count, DWORD listBase, BOOL unicode )
31 {
32 const GLDISPATCHTABLE * funcs = IntGetCurrentDispatchTable();
33 GLYPHMETRICS gm;
34 unsigned int glyph, size = 0;
35 void *bitmap = NULL, *gl_bitmap = NULL;
36 int org_alignment;
37 BOOL ret = TRUE;
38
39 funcs->GetIntegerv(GL_UNPACK_ALIGNMENT, &org_alignment);
40 funcs->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
41
42 for (glyph = first; glyph < first + count; glyph++) {
43 static const MAT2 identity = { {0,1},{0,0},{0,0},{0,1} };
44 unsigned int needed_size, height, width, width_int;
45
46 if (unicode)
47 needed_size = GetGlyphOutlineW(hdc, glyph, GGO_BITMAP, &gm, 0, NULL, &identity);
48 else
49 needed_size = GetGlyphOutlineA(hdc, glyph, GGO_BITMAP, &gm, 0, NULL, &identity);
50
51 TRACE("Glyph: %3d / List: %d size %d\n", glyph, listBase, needed_size);
52 if (needed_size == GDI_ERROR) {
53 ret = FALSE;
54 break;
55 }
56
57 if (needed_size > size) {
58 size = needed_size;
59 HeapFree(GetProcessHeap(), 0, bitmap);
60 HeapFree(GetProcessHeap(), 0, gl_bitmap);
61 bitmap = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
62 gl_bitmap = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
63 }
64 if (unicode)
65 ret = (GetGlyphOutlineW(hdc, glyph, GGO_BITMAP, &gm, size, bitmap, &identity) != GDI_ERROR);
66 else
67 ret = (GetGlyphOutlineA(hdc, glyph, GGO_BITMAP, &gm, size, bitmap, &identity) != GDI_ERROR);
68 if (!ret) break;
69
70 if (TRACE_ON(wgl)) {
71 unsigned int bitmask;
72 unsigned char *bitmap_ = bitmap;
73
74 TRACE(" - bbox: %d x %d\n", gm.gmBlackBoxX, gm.gmBlackBoxY);
75 TRACE(" - origin: (%d, %d)\n", gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y);
76 TRACE(" - increment: %d - %d\n", gm.gmCellIncX, gm.gmCellIncY);
77 if (needed_size != 0) {
78 TRACE(" - bitmap:\n");
79 for (height = 0; height < gm.gmBlackBoxY; height++) {
80 TRACE(" ");
81 for (width = 0, bitmask = 0x80; width < gm.gmBlackBoxX; width++, bitmask >>= 1) {
82 if (bitmask == 0) {
83 bitmap_ += 1;
84 bitmask = 0x80;
85 }
86 if (*bitmap_ & bitmask)
87 TRACE("*");
88 else
89 TRACE(" ");
90 }
91 bitmap_ += (4 - ((UINT_PTR)bitmap_ & 0x03));
92 TRACE("\n");
93 }
94 }
95 }
96
97 /* In OpenGL, the bitmap is drawn from the bottom to the top... So we need to invert the
98 * glyph for it to be drawn properly.
99 */
100 if (needed_size != 0) {
101 width_int = (gm.gmBlackBoxX + 31) / 32;
102 for (height = 0; height < gm.gmBlackBoxY; height++) {
103 for (width = 0; width < width_int; width++) {
104 ((int *) gl_bitmap)[(gm.gmBlackBoxY - height - 1) * width_int + width] =
105 ((int *) bitmap)[height * width_int + width];
106 }
107 }
108 }
109
110 funcs->NewList(listBase++, GL_COMPILE);
111 if (needed_size != 0) {
112 funcs->Bitmap(gm.gmBlackBoxX, gm.gmBlackBoxY,
113 0 - gm.gmptGlyphOrigin.x, (int) gm.gmBlackBoxY - gm.gmptGlyphOrigin.y,
114 gm.gmCellIncX, gm.gmCellIncY,
115 gl_bitmap);
116 } else {
117 /* This is the case of 'empty' glyphs like the space character */
118 funcs->Bitmap(0, 0, 0, 0, gm.gmCellIncX, gm.gmCellIncY, NULL);
119 }
120 funcs->EndList();
121 }
122
123 funcs->PixelStorei(GL_UNPACK_ALIGNMENT, org_alignment);
124 HeapFree(GetProcessHeap(), 0, bitmap);
125 HeapFree(GetProcessHeap(), 0, gl_bitmap);
126 return ret;
127 }
128
129 /***********************************************************************
130 * wglUseFontBitmapsA (OPENGL32.@)
131 */
wglUseFontBitmapsA(HDC hdc,DWORD first,DWORD count,DWORD listBase)132 BOOL WINAPI wglUseFontBitmapsA(HDC hdc, DWORD first, DWORD count, DWORD listBase)
133 {
134 return wglUseFontBitmaps_common( hdc, first, count, listBase, FALSE );
135 }
136
137 /***********************************************************************
138 * wglUseFontBitmapsW (OPENGL32.@)
139 */
wglUseFontBitmapsW(HDC hdc,DWORD first,DWORD count,DWORD listBase)140 BOOL WINAPI wglUseFontBitmapsW(HDC hdc, DWORD first, DWORD count, DWORD listBase)
141 {
142 return wglUseFontBitmaps_common( hdc, first, count, listBase, TRUE );
143 }
144
145 /* FIXME: should probably have a glu.h header */
146
147 typedef struct GLUtesselator GLUtesselator;
148 typedef void (WINAPI *_GLUfuncptr)(void);
149
150 #define GLU_TESS_BEGIN 100100
151 #define GLU_TESS_VERTEX 100101
152 #define GLU_TESS_END 100102
153
154 static GLUtesselator * (WINAPI *pgluNewTess)(void);
155 static void (WINAPI *pgluDeleteTess)(GLUtesselator *tess);
156 static void (WINAPI *pgluTessNormal)(GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z);
157 static void (WINAPI *pgluTessBeginPolygon)(GLUtesselator *tess, void *polygon_data);
158 static void (WINAPI *pgluTessEndPolygon)(GLUtesselator *tess);
159 static void (WINAPI *pgluTessCallback)(GLUtesselator *tess, GLenum which, _GLUfuncptr fn);
160 static void (WINAPI *pgluTessBeginContour)(GLUtesselator *tess);
161 static void (WINAPI *pgluTessEndContour)(GLUtesselator *tess);
162 static void (WINAPI *pgluTessVertex)(GLUtesselator *tess, GLdouble *location, GLvoid* data);
163
load_libglu(void)164 static HMODULE load_libglu(void)
165 {
166 static const WCHAR glu32W[] = {'g','l','u','3','2','.','d','l','l',0};
167 static int already_loaded;
168 static HMODULE module;
169
170 if (already_loaded) return module;
171 already_loaded = 1;
172
173 TRACE("Trying to load GLU library\n");
174 module = LoadLibraryW( glu32W );
175 if (!module)
176 {
177 WARN("Failed to load glu32\n");
178 return NULL;
179 }
180 #define LOAD_FUNCPTR(f) p##f = (void *)GetProcAddress( module, #f )
181 LOAD_FUNCPTR(gluNewTess);
182 LOAD_FUNCPTR(gluDeleteTess);
183 LOAD_FUNCPTR(gluTessBeginContour);
184 LOAD_FUNCPTR(gluTessNormal);
185 LOAD_FUNCPTR(gluTessBeginPolygon);
186 LOAD_FUNCPTR(gluTessCallback);
187 LOAD_FUNCPTR(gluTessEndContour);
188 LOAD_FUNCPTR(gluTessEndPolygon);
189 LOAD_FUNCPTR(gluTessVertex);
190 #undef LOAD_FUNCPTR
191 return module;
192 }
193
fixed_to_double(POINTFX fixed,UINT em_size,GLdouble vertex[3])194 static void fixed_to_double(POINTFX fixed, UINT em_size, GLdouble vertex[3])
195 {
196 vertex[0] = (fixed.x.value + (GLdouble)fixed.x.fract / (1 << 16)) / em_size;
197 vertex[1] = (fixed.y.value + (GLdouble)fixed.y.fract / (1 << 16)) / em_size;
198 vertex[2] = 0.0;
199 }
200
tess_callback_vertex(GLvoid * vertex)201 static void WINAPI tess_callback_vertex(GLvoid *vertex)
202 {
203 const GLDISPATCHTABLE * funcs = IntGetCurrentDispatchTable();
204 GLdouble *dbl = vertex;
205 TRACE("%f, %f, %f\n", dbl[0], dbl[1], dbl[2]);
206 funcs->Vertex3dv(vertex);
207 }
208
tess_callback_begin(GLenum which)209 static void WINAPI tess_callback_begin(GLenum which)
210 {
211 const GLDISPATCHTABLE * funcs = IntGetCurrentDispatchTable();
212 TRACE("%d\n", which);
213 funcs->Begin(which);
214 }
215
tess_callback_end(void)216 static void WINAPI tess_callback_end(void)
217 {
218 const GLDISPATCHTABLE * funcs = IntGetCurrentDispatchTable();
219 TRACE("\n");
220 funcs->End();
221 }
222
223 typedef struct _bezier_vector {
224 GLdouble x;
225 GLdouble y;
226 } bezier_vector;
227
bezier_deviation_squared(const bezier_vector * p)228 static double bezier_deviation_squared(const bezier_vector *p)
229 {
230 bezier_vector deviation;
231 bezier_vector vertex;
232 bezier_vector base;
233 double base_length;
234 double dot;
235
236 vertex.x = (p[0].x + p[1].x*2 + p[2].x)/4 - p[0].x;
237 vertex.y = (p[0].y + p[1].y*2 + p[2].y)/4 - p[0].y;
238
239 base.x = p[2].x - p[0].x;
240 base.y = p[2].y - p[0].y;
241
242 base_length = sqrt(base.x*base.x + base.y*base.y);
243 base.x /= base_length;
244 base.y /= base_length;
245
246 dot = base.x*vertex.x + base.y*vertex.y;
247 dot = min(max(dot, 0.0), base_length);
248 base.x *= dot;
249 base.y *= dot;
250
251 deviation.x = vertex.x-base.x;
252 deviation.y = vertex.y-base.y;
253
254 return deviation.x*deviation.x + deviation.y*deviation.y;
255 }
256
bezier_approximate(const bezier_vector * p,bezier_vector * points,FLOAT deviation)257 static int bezier_approximate(const bezier_vector *p, bezier_vector *points, FLOAT deviation)
258 {
259 bezier_vector first_curve[3];
260 bezier_vector second_curve[3];
261 bezier_vector vertex;
262 int total_vertices;
263
264 if(bezier_deviation_squared(p) <= deviation*deviation)
265 {
266 if(points)
267 *points = p[2];
268 return 1;
269 }
270
271 vertex.x = (p[0].x + p[1].x*2 + p[2].x)/4;
272 vertex.y = (p[0].y + p[1].y*2 + p[2].y)/4;
273
274 first_curve[0] = p[0];
275 first_curve[1].x = (p[0].x + p[1].x)/2;
276 first_curve[1].y = (p[0].y + p[1].y)/2;
277 first_curve[2] = vertex;
278
279 second_curve[0] = vertex;
280 second_curve[1].x = (p[2].x + p[1].x)/2;
281 second_curve[1].y = (p[2].y + p[1].y)/2;
282 second_curve[2] = p[2];
283
284 total_vertices = bezier_approximate(first_curve, points, deviation);
285 if(points)
286 points += total_vertices;
287 total_vertices += bezier_approximate(second_curve, points, deviation);
288 return total_vertices;
289 }
290
291 /***********************************************************************
292 * wglUseFontOutlines_common
293 */
wglUseFontOutlines_common(HDC hdc,DWORD first,DWORD count,DWORD listBase,FLOAT deviation,FLOAT extrusion,int format,LPGLYPHMETRICSFLOAT lpgmf,BOOL unicode)294 static BOOL wglUseFontOutlines_common(HDC hdc,
295 DWORD first,
296 DWORD count,
297 DWORD listBase,
298 FLOAT deviation,
299 FLOAT extrusion,
300 int format,
301 LPGLYPHMETRICSFLOAT lpgmf,
302 BOOL unicode)
303 {
304 const GLDISPATCHTABLE * funcs = IntGetCurrentDispatchTable();
305 UINT glyph;
306 const MAT2 identity = {{0,1},{0,0},{0,0},{0,1}};
307 GLUtesselator *tess = NULL;
308 LOGFONTW lf;
309 HFONT old_font, unscaled_font;
310 UINT em_size = 1024;
311 RECT rc;
312
313 TRACE("(%p, %d, %d, %d, %f, %f, %d, %p, %s)\n", hdc, first, count,
314 listBase, deviation, extrusion, format, lpgmf, unicode ? "W" : "A");
315
316 if(deviation <= 0.0)
317 deviation = 1.0/em_size;
318
319 if(format == WGL_FONT_POLYGONS)
320 {
321 if (!load_libglu())
322 {
323 ERR("glu32 is required for this function but isn't available\n");
324 return FALSE;
325 }
326
327 tess = pgluNewTess();
328 if(!tess) return FALSE;
329 pgluTessCallback(tess, GLU_TESS_VERTEX, (_GLUfuncptr)tess_callback_vertex);
330 pgluTessCallback(tess, GLU_TESS_BEGIN, (_GLUfuncptr)tess_callback_begin);
331 pgluTessCallback(tess, GLU_TESS_END, tess_callback_end);
332 }
333
334 GetObjectW(GetCurrentObject(hdc, OBJ_FONT), sizeof(lf), &lf);
335 rc.left = rc.right = rc.bottom = 0;
336 rc.top = em_size;
337 DPtoLP(hdc, (POINT*)&rc, 2);
338 lf.lfHeight = -abs(rc.top - rc.bottom);
339 lf.lfOrientation = lf.lfEscapement = 0;
340 unscaled_font = CreateFontIndirectW(&lf);
341 old_font = SelectObject(hdc, unscaled_font);
342
343 for (glyph = first; glyph < first + count; glyph++)
344 {
345 DWORD needed;
346 GLYPHMETRICS gm;
347 BYTE *buf;
348 TTPOLYGONHEADER *pph;
349 TTPOLYCURVE *ppc;
350 GLdouble *vertices = NULL, *vertices_temp = NULL;
351 int vertex_total = -1;
352
353 if(unicode)
354 needed = GetGlyphOutlineW(hdc, glyph, GGO_NATIVE, &gm, 0, NULL, &identity);
355 else
356 needed = GetGlyphOutlineA(hdc, glyph, GGO_NATIVE, &gm, 0, NULL, &identity);
357
358 if(needed == GDI_ERROR)
359 goto error;
360
361 buf = HeapAlloc(GetProcessHeap(), 0, needed);
362
363 if(!buf)
364 goto error;
365
366 if(unicode)
367 GetGlyphOutlineW(hdc, glyph, GGO_NATIVE, &gm, needed, buf, &identity);
368 else
369 GetGlyphOutlineA(hdc, glyph, GGO_NATIVE, &gm, needed, buf, &identity);
370
371 TRACE("glyph %d\n", glyph);
372
373 if(lpgmf)
374 {
375 lpgmf->gmfBlackBoxX = (float)gm.gmBlackBoxX / em_size;
376 lpgmf->gmfBlackBoxY = (float)gm.gmBlackBoxY / em_size;
377 lpgmf->gmfptGlyphOrigin.x = (float)gm.gmptGlyphOrigin.x / em_size;
378 lpgmf->gmfptGlyphOrigin.y = (float)gm.gmptGlyphOrigin.y / em_size;
379 lpgmf->gmfCellIncX = (float)gm.gmCellIncX / em_size;
380 lpgmf->gmfCellIncY = (float)gm.gmCellIncY / em_size;
381
382 TRACE("%fx%f at %f,%f inc %f,%f\n", lpgmf->gmfBlackBoxX, lpgmf->gmfBlackBoxY,
383 lpgmf->gmfptGlyphOrigin.x, lpgmf->gmfptGlyphOrigin.y, lpgmf->gmfCellIncX, lpgmf->gmfCellIncY);
384 lpgmf++;
385 }
386
387 funcs->NewList(listBase++, GL_COMPILE);
388 funcs->FrontFace(GL_CCW);
389 if(format == WGL_FONT_POLYGONS)
390 {
391 funcs->Normal3d(0.0, 0.0, 1.0);
392 pgluTessNormal(tess, 0, 0, 1);
393 pgluTessBeginPolygon(tess, NULL);
394 }
395
396 while(!vertices)
397 {
398 if(vertex_total != -1)
399 vertices_temp = vertices = HeapAlloc(GetProcessHeap(), 0, vertex_total * 3 * sizeof(GLdouble));
400 vertex_total = 0;
401
402 pph = (TTPOLYGONHEADER*)buf;
403 while((BYTE*)pph < buf + needed)
404 {
405 GLdouble previous[3];
406 fixed_to_double(pph->pfxStart, em_size, previous);
407
408 if(vertices)
409 TRACE("\tstart %d, %d\n", pph->pfxStart.x.value, pph->pfxStart.y.value);
410
411 if(format == WGL_FONT_POLYGONS)
412 pgluTessBeginContour(tess);
413 else
414 funcs->Begin(GL_LINE_LOOP);
415
416 if(vertices)
417 {
418 fixed_to_double(pph->pfxStart, em_size, vertices);
419 if(format == WGL_FONT_POLYGONS)
420 pgluTessVertex(tess, vertices, vertices);
421 else
422 funcs->Vertex3d(vertices[0], vertices[1], vertices[2]);
423 vertices += 3;
424 }
425 vertex_total++;
426
427 ppc = (TTPOLYCURVE*)((char*)pph + sizeof(*pph));
428 while((char*)ppc < (char*)pph + pph->cb)
429 {
430 int i, j;
431 int num;
432
433 switch(ppc->wType) {
434 case TT_PRIM_LINE:
435 for(i = 0; i < ppc->cpfx; i++)
436 {
437 if(vertices)
438 {
439 TRACE("\t\tline to %d, %d\n",
440 ppc->apfx[i].x.value, ppc->apfx[i].y.value);
441 fixed_to_double(ppc->apfx[i], em_size, vertices);
442 if(format == WGL_FONT_POLYGONS)
443 pgluTessVertex(tess, vertices, vertices);
444 else
445 funcs->Vertex3d(vertices[0], vertices[1], vertices[2]);
446 vertices += 3;
447 }
448 fixed_to_double(ppc->apfx[i], em_size, previous);
449 vertex_total++;
450 }
451 break;
452
453 case TT_PRIM_QSPLINE:
454 for(i = 0; i < ppc->cpfx-1; i++)
455 {
456 bezier_vector curve[3];
457 bezier_vector *points;
458 GLdouble curve_vertex[3];
459
460 if(vertices)
461 TRACE("\t\tcurve %d,%d %d,%d\n",
462 ppc->apfx[i].x.value, ppc->apfx[i].y.value,
463 ppc->apfx[i + 1].x.value, ppc->apfx[i + 1].y.value);
464
465 curve[0].x = previous[0];
466 curve[0].y = previous[1];
467 fixed_to_double(ppc->apfx[i], em_size, curve_vertex);
468 curve[1].x = curve_vertex[0];
469 curve[1].y = curve_vertex[1];
470 fixed_to_double(ppc->apfx[i + 1], em_size, curve_vertex);
471 curve[2].x = curve_vertex[0];
472 curve[2].y = curve_vertex[1];
473 if(i < ppc->cpfx-2)
474 {
475 curve[2].x = (curve[1].x + curve[2].x)/2;
476 curve[2].y = (curve[1].y + curve[2].y)/2;
477 }
478 num = bezier_approximate(curve, NULL, deviation);
479 points = HeapAlloc(GetProcessHeap(), 0, num*sizeof(bezier_vector));
480 num = bezier_approximate(curve, points, deviation);
481 vertex_total += num;
482 if(vertices)
483 {
484 for(j=0; j<num; j++)
485 {
486 TRACE("\t\t\tvertex at %f,%f\n", points[j].x, points[j].y);
487 vertices[0] = points[j].x;
488 vertices[1] = points[j].y;
489 vertices[2] = 0.0;
490 if(format == WGL_FONT_POLYGONS)
491 pgluTessVertex(tess, vertices, vertices);
492 else
493 funcs->Vertex3d(vertices[0], vertices[1], vertices[2]);
494 vertices += 3;
495 }
496 }
497 HeapFree(GetProcessHeap(), 0, points);
498 previous[0] = curve[2].x;
499 previous[1] = curve[2].y;
500 }
501 break;
502 default:
503 ERR("\t\tcurve type = %d\n", ppc->wType);
504 if(format == WGL_FONT_POLYGONS)
505 pgluTessEndContour(tess);
506 else
507 funcs->End();
508 goto error_in_list;
509 }
510
511 ppc = (TTPOLYCURVE*)((char*)ppc + sizeof(*ppc) +
512 (ppc->cpfx - 1) * sizeof(POINTFX));
513 }
514 if(format == WGL_FONT_POLYGONS)
515 pgluTessEndContour(tess);
516 else
517 funcs->End();
518 pph = (TTPOLYGONHEADER*)((char*)pph + pph->cb);
519 }
520 }
521
522 error_in_list:
523 if(format == WGL_FONT_POLYGONS)
524 pgluTessEndPolygon(tess);
525 funcs->Translated((GLdouble)gm.gmCellIncX / em_size, (GLdouble)gm.gmCellIncY / em_size, 0.0);
526 funcs->EndList();
527
528 HeapFree(GetProcessHeap(), 0, buf);
529
530 if(vertices_temp)
531 HeapFree(GetProcessHeap(), 0, vertices_temp);
532 }
533
534 error:
535 DeleteObject(SelectObject(hdc, old_font));
536 if(format == WGL_FONT_POLYGONS)
537 pgluDeleteTess(tess);
538 return TRUE;
539
540 }
541
542 /***********************************************************************
543 * wglUseFontOutlinesA (OPENGL32.@)
544 */
wglUseFontOutlinesA(HDC hdc,DWORD first,DWORD count,DWORD listBase,FLOAT deviation,FLOAT extrusion,int format,LPGLYPHMETRICSFLOAT lpgmf)545 BOOL WINAPI wglUseFontOutlinesA(HDC hdc,
546 DWORD first,
547 DWORD count,
548 DWORD listBase,
549 FLOAT deviation,
550 FLOAT extrusion,
551 int format,
552 LPGLYPHMETRICSFLOAT lpgmf)
553 {
554 return wglUseFontOutlines_common(hdc, first, count, listBase, deviation, extrusion, format, lpgmf, FALSE);
555 }
556
557 /***********************************************************************
558 * wglUseFontOutlinesW (OPENGL32.@)
559 */
wglUseFontOutlinesW(HDC hdc,DWORD first,DWORD count,DWORD listBase,FLOAT deviation,FLOAT extrusion,int format,LPGLYPHMETRICSFLOAT lpgmf)560 BOOL WINAPI wglUseFontOutlinesW(HDC hdc,
561 DWORD first,
562 DWORD count,
563 DWORD listBase,
564 FLOAT deviation,
565 FLOAT extrusion,
566 int format,
567 LPGLYPHMETRICSFLOAT lpgmf)
568 {
569 return wglUseFontOutlines_common(hdc, first, count, listBase, deviation, extrusion, format, lpgmf, TRUE);
570 }
571