24#include "GlobalParams.h"
25#include "UnicodeMap.h"
27#include "TextOutputDev.h"
36#define textOutSpace 0.2
37#define textOutColSpace 0.2
41struct TextOutColumnEdge {
49TextBlock::TextBlock() {
58TextBlock::~TextBlock() {
61 for (p1 = strings; p1; p1 = p2) {
79TextLine::~TextLine() {
82 for (p1 = blocks; p1; p1 = p2) {
92TextString::TextString(GfxState *state,
double x0,
double y0,
97 state->transform(x0, y0, &x, &y);
98 if ((font = state->getFont())) {
99 yMin = y - font->getAscent() * fontSize;
100 yMax = y - font->getDescent() * fontSize;
104 yMin = y - 0.95 * fontSize;
105 yMax = y + 0.35 * fontSize;
121TextString::~TextString() {
126void TextString::addChar(GfxState *,
double x,
double ,
127 double dx,
double , Unicode u) {
130 text = (Unicode *)grealloc(text, size *
sizeof(Unicode));
131 xRight = (
double *)grealloc(xRight, size *
sizeof(
double));
137 xMax = xRight[len] = x + dx;
145TextPage::TextPage(GBool rawOrderA) {
146 rawOrder = rawOrderA;
150 xyCur1 = xyCur2 = NULL;
156TextPage::~TextPage() {
160void TextPage::updateFont(GfxState *state) {
164 int code, mCode, letterCode, anyCode;
168 fontSize = state->getTransformedFontSize();
169 if ((font = state->getFont()) && font->getType() == fontType3) {
176 mCode = letterCode = anyCode = -1;
177 for (code = 0; code < 256; ++code) {
178 name = ((Gfx8BitFont *)font)->getCharName(code);
179 if (name && name[0] ==
'm' && name[1] ==
'\0') {
182 if (letterCode < 0 && name && name[1] ==
'\0' &&
183 ((name[0] >=
'A' && name[0] <=
'Z') ||
184 (name[0] >=
'a' && name[0] <=
'z'))) {
187 if (anyCode < 0 && name && ((Gfx8BitFont *)font)->getWidth(code) > 0) {
192 (w = ((Gfx8BitFont *)font)->getWidth(mCode)) > 0) {
195 }
else if (letterCode >= 0 &&
196 (w = ((Gfx8BitFont *)font)->getWidth(letterCode)) > 0) {
199 }
else if (anyCode >= 0 &&
200 (w = ((Gfx8BitFont *)font)->getWidth(anyCode)) > 0) {
204 fm = font->getFontMatrix();
206 fontSize *= fabs(fm[3] / fm[0]);
211void TextPage::beginString(GfxState *state,
double x0,
double y0) {
219 curStr =
new TextString(state, x0, y0, fontSize);
222void TextPage::addChar(GfxState *state,
double x,
double y,
223 double dx,
double dy, Unicode *u,
int uLen) {
224 double x1, y1, w1, h1, dx2, dy2;
227 state->transform(x, y, &x1, &y1);
228 if (x1 < 0 || x1 > state->getPageWidth() ||
229 y1 < 0 || y1 > state->getPageHeight()) {
232 state->textTransformDelta(state->getCharSpace() * state->getHorizScaling(),
236 state->transformDelta(dx, dy, &w1, &h1);
237 if (!globalParams->getTextKeepTinyChars() &&
238 fabs(w1) < 3 && fabs(h1) < 3) {
239 if (++nTinyChars > 20000) {
244 if (n > 0 && x1 - curStr->xRight[n-1] >
245 0.1 * (curStr->yMax - curStr->yMin)) {
248 beginString(state, x, y);
250 if (uLen == 1 && u[0] == (Unicode)0x20 &&
251 w1 > 0.5 * (curStr->yMax - curStr->yMin)) {
259 for (i = 0; i < uLen; ++i) {
260 curStr->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, u[i]);
264void TextPage::endString() {
276void TextPage::addString(TextString *str) {
290 }
else if ((!xyCur1 || xyBefore(xyCur1, str)) &&
291 (!xyCur2 || xyBefore(str, xyCur2))) {
294 }
else if (xyCur1 && xyBefore(xyCur1, str)) {
295 for (p1 = xyCur1, p2 = xyCur2; p2; p1 = p2, p2 = p2->next) {
296 if (xyBefore(str, p2)) {
302 for (p1 = NULL, p2 = xyStrings; p2; p1 = p2, p2 = p2->next) {
303 if (xyBefore(str, p2)) {
318void TextPage::coalesce() {
319 TextLine *line, *line0;
320 TextBlock *yxBlocks, *xyBlocks, *blk, *blk0, *blk1, *blk2;
321 TextString *str0, *str1, *str2, *str3, *str4;
322 TextString *str1prev, *str2prev, *str3prev;
323 TextOutColumnEdge *edges;
327 int edgesLength, edgesSize;
328 double x, yMin, yMax;
329 double space, fit1, fit2, h;
334 for (str1 = xyStrings; str1; str1 = str1->next) {
335 printf(
"x=%.2f..%.2f y=%.2f..%.2f size=%.2f '",
336 str1->xMin, str1->xMax, str1->yMin, str1->yMax,
337 (str1->yMax - str1->yMin));
338 for (i = 0; i < str1->len; ++i) {
339 fputc(str1->text[i] & 0xff, stdout);
343 printf(
"\n------------------------------------------------------------\n\n");
348 edgesLength = edgesSize = 0;
350 for (str1prev = NULL, str1 = xyStrings;
352 str1prev = str1, str1 = str1->next) {
356 h = str1->yMax - str1->yMin;
357 if (str1prev && (str1->xMin - str1prev->xMax) / h < textOutColSpace) {
363 for (str2prev = str1, str2 = str1->next;
365 str2prev = str2, str2 = str2->next) {
366 h = str2->yMax - str2->yMin;
368 (str2->xMin - str2prev->xMax) / h > textOutColSpace &&
369 fabs(str2->xMin - x) < 0.5 &&
370 str2->yMin - yMax < 0.3 * h &&
371 yMin - str2->yMax < 0.3 * h) {
376 if (str2->yMin < yMin) {
379 if (str2->yMax > yMax) {
382 str2->marked = gTrue;
383 for (str3prev = str1, str3 = str1->next;
385 str3prev = str3, str3 = str3->next) {
386 h = str3->yMax - str3->yMin;
388 (str3->xMin - str3prev->xMax) / h > textOutColSpace &&
389 fabs(str3->xMin - x) < 0.5 &&
390 str3->yMin - yMax < 0.3 * h &&
391 yMin - str3->yMax < 0.3 * h) {
396 if (str3->yMin < yMin) {
399 if (str3->yMax > yMax) {
402 str3->marked = gTrue;
404 for (str2prev = str1, str2 = str1->next;
406 str2prev = str2, str2 = str2->next) {
407 h = str2->yMax - str2->yMin;
409 (str2->xMin - str2prev->xMax) / h > textOutColSpace &&
410 fabs(str2->xMin - x) < 0.5 &&
411 str2->yMin - yMax < 0.3 * h &&
412 yMin - str2->yMax < 0.3 * h) {
413 if (str2->yMin < yMin) {
416 if (str2->yMax > yMax) {
419 str2->marked = gTrue;
424 if (edgesLength == edgesSize) {
425 edgesSize = edgesSize ? 2 * edgesSize : 16;
426 edges = (TextOutColumnEdge *)
427 grealloc(edges, edgesSize *
sizeof(TextOutColumnEdge));
429 edges[edgesLength].x = x;
430 edges[edgesLength].y0 = yMin;
431 edges[edgesLength].y1 = yMax;
434 str2->marked = gFalse;
437 str1->marked = gTrue;
442 printf(
"column edges:\n");
443 for (i = 0; i < edgesLength; ++i) {
444 printf(
"%d: x=%.2f y0=%.2f y1=%.2f\n",
445 i, edges[i].x, edges[i].y0, edges[i].y1);
447 printf(
"\n------------------------------------------------------------\n\n");
457 xyStrings = xyStrings->next;
459 blk =
new TextBlock();
461 blk->xMin = str0->xMin;
462 blk->xMax = str0->xMax;
463 blk->yMin = str0->yMin;
464 blk->yMax = str0->yMax;
468 fit1 = coalesceFit(str0, str2);
471 space = str0->yMax - str0->yMin;
472 for (str3 = xyStrings, str4 = xyStrings->next;
473 str4 && str4->xMin - str0->xMax <= space;
474 str3 = str4, str4 = str4->next) {
475 fit2 = coalesceFit(str0, str4);
490 for (i = 0; i < edgesLength; ++i) {
491 if (str0->xMax < edges[i].x + 0.5 && edges[i].x - 0.5 < str2->xMin &&
492 str0->yMin < edges[i].y1 && str0->yMax > edges[i].y0 &&
493 str2->yMin < edges[i].y1 && str2->yMax > edges[i].y0) {
497 if (i < edgesLength) {
503 str1->next = str2->next;
505 xyStrings = str2->next;
509 if (str2->xMax > blk->xMax) {
510 blk->xMax = str2->xMax;
512 if (str2->yMin < blk->yMin) {
513 blk->yMin = str2->yMin;
515 if (str2->yMax > blk->yMax) {
516 blk->yMax = str2->yMax;
524 for (blk1 = NULL, blk2 = yxBlocks;
525 blk2 && !yxBefore(blk, blk2);
526 blk1 = blk2, blk2 = blk2->next) ;
543 uMap = globalParams->getTextEncoding();
544 isUnicode = uMap ? uMap->isUnicode() : gFalse;
545 for (blk = yxBlocks; blk; blk = blk->next) {
547 for (str1 = blk->strings; str1; str1 = str1->next) {
548 blk->len += str1->len;
549 if (str1->next && str1->next->xMin - str1->xMax >
550 textOutSpace * (str1->yMax - str1->yMin)) {
551 str1->spaceAfter = gTrue;
554 str1->spaceAfter = gFalse;
557 blk->text = (Unicode *)gmalloc(blk->len *
sizeof(Unicode));
558 blk->xRight = (
double *)gmalloc(blk->len *
sizeof(
double));
559 blk->col = (
int *)gmalloc(blk->len *
sizeof(
int));
561 for (str1 = blk->strings; str1; str1 = str1->next) {
562 for (j = 0; j < str1->len; ++j) {
563 blk->text[i] = str1->text[j];
564 blk->xRight[i] = str1->xRight[j];
567 if (str1->spaceAfter) {
568 blk->text[i] = (Unicode)0x0020;
569 blk->xRight[i] = str1->next->xMin;
573 blk->convertedLen = 0;
574 for (j = 0; j < blk->len; ++j) {
575 blk->col[j] = blk->convertedLen;
579 blk->convertedLen += uMap->mapUnicode(blk->text[j], buf,
sizeof(buf));
588 for (blk = yxBlocks; blk; blk = blk->next) {
589 printf(
"[block: x=%.2f..%.2f y=%.2f..%.2f len=%d]\n",
590 blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->len);
592 for (str = blk->strings; str; str = str->next) {
593 printf(
" x=%.2f..%.2f y=%.2f..%.2f size=%.2f'",
594 str->xMin, str->xMax, str->yMin, str->yMax,
595 (str->yMax - str->yMin));
596 for (i = 0; i < str->len; ++i) {
597 fputc(str->text[i] & 0xff, stdout);
599 if (str->spaceAfter) {
605 printf(
"\n------------------------------------------------------------\n\n");
613 yxBlocks = yxBlocks->next;
615 line =
new TextLine();
617 line->yMin = blk0->yMin;
618 line->yMax = blk0->yMax;
622 h = blk0->yMax - blk0->yMin;
623 if (yxBlocks->len == blk0->len &&
624 !memcmp(yxBlocks->text, blk0->text,
625 yxBlocks->len *
sizeof(Unicode)) &&
626 fabs(yxBlocks->yMin - blk0->yMin) / h < 0.2 &&
627 fabs(yxBlocks->yMax - blk0->yMax) / h < 0.2 &&
628 fabs(yxBlocks->xMin - blk0->xMin) / h < 0.2 &&
629 fabs(yxBlocks->xMax - blk0->xMax) / h < 0.2) {
631 yxBlocks = yxBlocks->next;
636 if (rawOrder && yxBlocks->yMax < blk0->yMin) {
639 if (yxBlocks->yMin > 0.2*blk0->yMin + 0.8*blk0->yMax ||
640 yxBlocks->xMin < blk0->xMax) {
644 yxBlocks = yxBlocks->next;
647 if (blk1->yMin < line->yMin) {
648 line->yMin = blk1->yMin;
650 if (blk1->yMax > line->yMax) {
651 line->yMax = blk1->yMax;
667 for (line = lines; line; line = line->next) {
668 for (blk = line->blocks; blk; blk = blk->next) {
669 for (blk1 = NULL, blk2 = xyBlocks;
670 blk2 && !xyBefore(blk, blk2);
671 blk1 = blk2, blk2 = blk2->xyNext) ;
682 for (blk = xyBlocks; blk; blk = blk->xyNext) {
683 printf(
"[block: x=%.2f..%.2f y=%.2f..%.2f len=%d]\n",
684 blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->len);
686 for (str = blk->strings; str; str = str->next) {
687 printf(
" x=%.2f..%.2f y=%.2f..%.2f size=%.2f '",
688 str->xMin, str->xMax, str->yMin, str->yMax,
689 (str->yMax - str->yMin));
690 for (i = 0; i < str->len; ++i) {
691 fputc(str->text[i] & 0xff, stdout);
696 printf(
"\n------------------------------------------------------------\n\n");
700 for (blk1 = xyBlocks; blk1; blk1 = blk1->xyNext) {
702 for (blk2 = xyBlocks; blk2 != blk1; blk2 = blk2->xyNext) {
703 if (blk1->xMin >= blk2->xMax) {
704 d = (int)((blk1->xMin - blk2->xMax) /
705 (0.4 * (blk1->yMax - blk1->yMin)));
709 col2 = blk2->col[0] + blk2->convertedLen + d;
713 }
else if (blk1->xMin > blk2->xMin) {
714 for (i = 0; i < blk2->len && blk1->xMin >= blk2->xRight[i]; ++i) ;
721 for (j = 0; j < blk1->len; ++j) {
722 blk1->col[j] += col1;
727 for (line = lines; line; line = line->next) {
729 for (blk = line->blocks; blk; blk = blk->next) {
730 printf(
"[block: col=%d, len=%d]\n", blk->col[0], blk->len);
732 for (str = blk->strings; str; str = str->next) {
733 printf(
" x=%.2f..%.2f y=%.2f..%.2f size=%.2f '",
734 str->xMin, str->xMax, str->yMin, str->yMax,
735 (str->yMax - str->yMin));
736 for (i = 0; i < str->len; ++i) {
737 fputc(str->text[i] & 0xff, stdout);
739 if (str->spaceAfter) {
740 printf(
" [space]\n");
746 printf(
"\n------------------------------------------------------------\n\n");
751GBool TextPage::findText(Unicode *s,
int len,
752 GBool top, GBool bottom,
753 double *xMin,
double *yMin,
754 double *xMax,
double *yMax) {
763 for (line = lines; line; line = line->next) {
764 for (blk = line->blocks; blk; blk = blk->next) {
767 if (!top && (blk->yMax < *yMin ||
768 (blk->yMin < *yMin && blk->xMax <= *xMin))) {
773 if (!bottom && (blk->yMin > *yMax ||
774 (blk->yMax > *yMax && blk->xMin >= *xMax))) {
780 for (i = 0, p = blk->text; i <= m - len; ++i, ++p) {
782 x0 = (i == 0) ? blk->xMin : blk->xRight[i-1];
787 if (!top && blk->yMin < *yMin) {
794 if (!bottom && blk->yMax > *yMax) {
801 for (j = 0; j < len; ++j) {
804 if (p[j] >= 0x41 && p[j] <= 0x5a) {
809 if (s[j] >= 0x41 && s[j] <= 0x5a) {
823 *xMax = blk->xRight[i + len - 1];
835GString *TextPage::getText(
double xMin,
double yMin,
836 double xMax,
double yMax) {
840 char space[8], eol[16], buf[8];
841 int spaceLen, eolLen, len;
845 int firstCol, col, i;
851 if (!(uMap = globalParams->getTextEncoding())) {
854 isUnicode = uMap->isUnicode();
855 spaceLen = uMap->mapUnicode(0x20, space,
sizeof(space));
857 switch (globalParams->getTextEOL()) {
859 eolLen = uMap->mapUnicode(0x0a, eol,
sizeof(eol));
862 eolLen = uMap->mapUnicode(0x0d, eol,
sizeof(eol));
863 eolLen += uMap->mapUnicode(0x0a, eol + eolLen,
sizeof(eol) - eolLen);
866 eolLen = uMap->mapUnicode(0x0d, eol,
sizeof(eol));
873 for (line = lines; line; line = line->next) {
874 if (line->yMin > yMax) {
877 if (line->yMax < yMin) {
881 for (blk = line->blocks; blk && blk->xMax < xMin; blk = blk->next) ;
882 if (!blk || blk->xMin > xMax) {
886 y = 0.5 * (blk->yMin + blk->yMax);
887 if (y < yMin || y > yMax) {
897 x0 = (i==0) ? blk->xMin : blk->xRight[i-1];
899 if (0.5 * (x0 + x1) > xMin) {
906 if (firstCol < 0 || col < firstCol) {
912 for (line = lines; line; line = line->next) {
913 if (line->yMin > yMax) {
916 if (line->yMax < yMin) {
920 for (blk = line->blocks; blk && blk->xMax < xMin; blk = blk->next) ;
921 if (!blk || blk->xMin > xMax) {
925 y = 0.5 * (blk->yMin + blk->yMax);
926 if (y < yMin || y > yMax) {
932 x0 = (i==0) ? blk->xMin : blk->xRight[i-1];
934 if (0.5 * (x0 + x1) > xMin) {
945 for (; col < blk->col[i]; ++col) {
946 s->append(space, spaceLen);
950 for (; i < blk->len; ++i) {
952 x0 = (i==0) ? blk->xMin : blk->xRight[i-1];
954 if (0.5 * (x0 + x1) > xMax) {
958 len = uMap->mapUnicode(blk->text[i], buf,
sizeof(buf));
960 col += isUnicode ? 1 : len;
970 }
while (blk && blk->xMin < xMax);
973 s->append(eol, eolLen);
982void TextPage::dump(
void *outputStream, TextOutputFunc outputFunc) {
984 char space[8], eol[16], eop[8], buf[8];
985 int spaceLen, eolLen, eopLen, len;
991 if (!(uMap = globalParams->getTextEncoding())) {
994 spaceLen = uMap->mapUnicode(0x20, space,
sizeof(space));
996 switch (globalParams->getTextEOL()) {
998 eolLen = uMap->mapUnicode(0x0a, eol,
sizeof(eol));
1001 eolLen = uMap->mapUnicode(0x0d, eol,
sizeof(eol));
1002 eolLen += uMap->mapUnicode(0x0a, eol + eolLen,
sizeof(eol) - eolLen);
1005 eolLen = uMap->mapUnicode(0x0d, eol,
sizeof(eol));
1008 eopLen = uMap->mapUnicode(0x0c, eop,
sizeof(eop));
1011 for (line = lines; line; line = line->next) {
1013 for (blk = line->blocks; blk; blk = blk->next) {
1016 if (rawOrder && col == 0) {
1019 for (; col < blk->col[0]; ++col) {
1020 (*outputFunc)(outputStream, space, spaceLen);
1025 for (i = 0; i < blk->len; ++i) {
1026 len = uMap->mapUnicode(blk->text[i], buf,
sizeof(buf));
1027 (*outputFunc)(outputStream, buf, len);
1029 col += blk->convertedLen;
1033 (*outputFunc)(outputStream, eol, eolLen);
1037 d = (int)((line->next->yMin - line->yMax) /
1038 (line->blocks->strings->yMax - lines->blocks->strings->yMin)
1042 if (rawOrder && d > 2) {
1044 }
else if (!rawOrder && d > 5) {
1047 for (; d > 0; --d) {
1048 (*outputFunc)(outputStream, eol, eolLen);
1054 (*outputFunc)(outputStream, eol, eolLen);
1055 (*outputFunc)(outputStream, eop, eopLen);
1056 (*outputFunc)(outputStream, eol, eolLen);
1063GBool TextPage::xyBefore(TextString *str1, TextString *str2) {
1064 return str1->xMin < str2->xMin ||
1065 (str1->xMin == str2->xMin && str1->yMin < str2->yMin);
1070GBool TextPage::xyBefore(TextBlock *blk1, TextBlock *blk2) {
1071 return blk1->xMin < blk2->xMin ||
1072 (blk1->xMin == blk2->xMin && blk1->yMin < blk2->yMin);
1077GBool TextPage::yxBefore(TextBlock *blk1, TextBlock *blk2) {
1078 double h1, h2, overlap;
1080 h1 = blk1->yMax - blk1->yMin;
1081 h2 = blk2->yMax - blk2->yMin;
1082 overlap = ((blk1->yMax < blk2->yMax ? blk1->yMax : blk2->yMax) -
1083 (blk1->yMin > blk2->yMin ? blk1->yMin : blk2->yMin)) /
1084 (h1 < h2 ? h1 : h2);
1085 if (overlap > 0.6) {
1086 return blk1->xMin < blk2->xMin;
1088 return blk1->yMin < blk2->yMin;
1091double TextPage::coalesceFit(TextString *str1, TextString *str2) {
1092 double h1, h2, w1, w2, r, overlap, spacing;
1094 h1 = str1->yMax - str1->yMin;
1095 h2 = str2->yMax - str2->yMin;
1096 w1 = str1->xMax - str1->xMin;
1097 w2 = str2->xMax - str2->xMin;
1099 if (r < (1.0 / 3.0) || r > 3) {
1102 overlap = ((str1->yMax < str2->yMax ? str1->yMax : str2->yMax) -
1103 (str1->yMin > str2->yMin ? str1->yMin : str2->yMin)) /
1104 (h1 < h2 ? h1 : h2);
1105 if (overlap < 0.5) {
1108 spacing = (str2->xMin - str1->xMax) / (h1 > h2 ? h1 : h2);
1109 if (spacing < -0.5) {
1114 if ((str2->xMin - str1->xMax) / (w1 < w2 ? w1 : w2) < -0.7) {
1120void TextPage::clear() {
1122 TextString *s1, *s2;
1129 for (p1 = lines; p1; p1 = p2) {
1133 }
else if (xyStrings) {
1134 for (s1 = xyStrings; s1; s1 = s2) {
1140 xyCur1 = xyCur2 = NULL;
1150static void outputToFile(
void *stream,
char *text,
int len) {
1151 fwrite(text, 1, len, (FILE *)stream);
1154TextOutputDev::TextOutputDev(
char *fileName, GBool rawOrderA, GBool append) {
1156 rawOrder = rawOrderA;
1162 if (!strcmp(fileName,
"-")) {
1163 outputStream = stdout;
1164 }
else if ((outputStream = fopen(fileName, append ?
"ab" :
"wb"))) {
1167 error(-1,
"Couldn't open text file '%s'", fileName);
1171 outputFunc = &outputToFile;
1173 outputStream = NULL;
1177 text =
new TextPage(rawOrder);
1180TextOutputDev::TextOutputDev(TextOutputFunc func,
void *stream,
1183 outputStream = stream;
1185 rawOrder = rawOrderA;
1186 text =
new TextPage(rawOrder);
1190TextOutputDev::~TextOutputDev() {
1193 ICS_MapRefNumAndAssign((
short)((FILE *)outputStream)->handle);
1195 fclose((FILE *)outputStream);
1202void TextOutputDev::startPage(
int , GfxState *) {
1206void TextOutputDev::endPage() {
1209 text->dump(outputStream, outputFunc);
1213void TextOutputDev::updateFont(GfxState *state) {
1214 text->updateFont(state);
1217void TextOutputDev::beginString(GfxState *state, GString *) {
1218 text->beginString(state, state->getCurX(), state->getCurY());
1221void TextOutputDev::endString(GfxState *) {
1225void TextOutputDev::drawChar(GfxState *state,
double x,
double y,
1226 double dx,
double dy,
1228 CharCode , Unicode *u,
int uLen) {
1229 text->addChar(state, x, y, dx, dy, u, uLen);
1232GBool TextOutputDev::findText(Unicode *s,
int len,
1233 GBool top, GBool bottom,
1234 double *xMin,
double *yMin,
1235 double *xMax,
double *yMax) {
1236 return text->findText(s, len, top, bottom, xMin, yMin, xMax, yMax);
1239GString *TextOutputDev::getText(
double xMin,
double yMin,
1240 double xMax,
double yMax) {
1241 return text->getText(xMin, yMin, xMax, yMax);