1/*
2 SPDX-FileCopyrightText: 2002-2010 Anders Lund <anders@alweb.dk>
3
4 Rewritten based on code of:
5 SPDX-FileCopyrightText: 2002 Michael Goffioul <kdeprint@swing.be>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "printpainter.h"
11
12#include "katebuffer.h"
13#include "katedocument.h"
14#include "katehighlight.h"
15#include "katepartdebug.h"
16#include "katerenderer.h"
17#include "katetextfolding.h"
18#include "katetextlayout.h"
19#include "kateview.h"
20
21#include <KLocalizedString>
22#include <KUser>
23
24#include <QPainter>
25#include <QPrinter>
26
27using namespace KatePrinter;
28
29class KatePrinter::PageLayout
30{
31public:
32 PageLayout()
33 : headerTagList()
34 , footerTagList()
35 , selectionRange()
36 {
37 }
38
39 uint pageWidth = 0;
40 uint pageHeight = 0;
41 uint headerWidth = 0;
42 uint maxWidth = 0;
43 uint maxHeight = 0;
44 int xstart = 0; // beginning point for painting lines
45 int innerMargin = 0;
46
47 bool selectionOnly = false;
48
49 uint firstline = 0;
50 uint lastline = 0;
51
52 // Header/Footer Page
53 uint headerHeight = 0;
54 QStringList headerTagList;
55 uint footerHeight = 0;
56 QStringList footerTagList;
57
58 KTextEditor::Range selectionRange;
59};
60
61PrintPainter::PrintPainter(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view)
62 : m_view(view)
63 , m_doc(doc)
64 , m_printGuide(false)
65 , m_printLineNumbers(false)
66 , m_useHeader(false)
67 , m_useFooter(false)
68 , m_useBackground(false)
69 , m_useBox(false)
70 , m_useHeaderBackground(false)
71 , m_useFooterBackground(false)
72 , m_boxMargin(0)
73 , m_boxWidth(1)
74 , m_boxColor(Qt::black)
75 , m_headerBackground(Qt::lightGray)
76 , m_headerForeground(Qt::black)
77 , m_footerBackground(Qt::lightGray)
78 , m_footerForeground(Qt::black)
79 , m_fhFont()
80 , m_headerFormat()
81 , m_footerFormat()
82{
83 m_renderer = new KateRenderer(m_doc, m_view->renderer()->folding(), m_view);
84 m_renderer->setPrinterFriendly(true);
85
86 updateCache();
87}
88
89PrintPainter::~PrintPainter()
90{
91 delete m_renderer;
92}
93
94void PrintPainter::setTextFont(const QFont &font)
95{
96 m_view->rendererConfig()->setFont(font);
97}
98
99void PrintPainter::setUseBox(const bool on)
100{
101 m_useBox = on;
102 setBoxWidth(m_boxWidth); // reset the width
103}
104
105void PrintPainter::setBoxWidth(const int width)
106{
107 if (m_useBox) {
108 m_boxWidth = width;
109 if (width < 1) {
110 m_boxWidth = 1;
111 }
112 } else {
113 m_boxWidth = 0;
114 }
115}
116
117void PrintPainter::setBoxColor(const QColor &color)
118{
119 if (color.isValid()) {
120 m_boxColor = color;
121 }
122}
123
124void PrintPainter::setHeaderBackground(const QColor &color)
125{
126 if (color.isValid()) {
127 m_headerBackground = color;
128 }
129}
130
131void PrintPainter::setHeaderForeground(const QColor &color)
132{
133 if (color.isValid()) {
134 m_headerForeground = color;
135 }
136}
137
138void PrintPainter::setFooterBackground(const QColor &color)
139{
140 if (color.isValid()) {
141 m_footerBackground = color;
142 }
143}
144void PrintPainter::setFooterForeground(const QColor &color)
145{
146 if (color.isValid()) {
147 m_footerForeground = color;
148 }
149}
150
151void PrintPainter::setColorScheme(const QString &scheme)
152{
153 // directly set that for the renderer
154 m_view->rendererConfig()->setSchema(scheme);
155
156 // changed renderer requires cache updates
157 updateCache();
158}
159
160void PrintPainter::updateCache()
161{
162 m_fontHeight = m_renderer->fontHeight();
163
164 // figure out the horizontal space required
165 QString s = QStringLiteral("%1 ").arg(a: m_doc->lines());
166 s.fill(c: QLatin1Char('5'), size: -1); // some non-fixed fonts haven't equally wide numbers
167 // FIXME calculate which is actually the widest...
168 m_lineNumberWidth = m_renderer->currentFontMetrics().boundingRect(string: s).width();
169}
170
171void PrintPainter::paint(QPrinter *printer) const
172{
173 QPainter painter(printer);
174 PageLayout pl;
175
176 configure(printer, layout&: pl);
177
178 uint lineCount = pl.firstline;
179 uint y = 0;
180 uint currentPage = (printer->fromPage() == 0) ? 1 : printer->fromPage();
181 bool pageStarted = true;
182 uint remainder = 0;
183
184 auto &f = m_view->renderer()->folding();
185
186 // On to draw something :-)
187 while (lineCount <= pl.lastline) {
188 if (y + m_fontHeight > pl.maxHeight) {
189 if ((int)currentPage == printer->toPage()) { // we've reached the page break of last page to be printed
190 break;
191 }
192 printer->newPage();
193 painter.resetTransform();
194 currentPage++;
195 pageStarted = true;
196 y = 0;
197 }
198
199 if (pageStarted) {
200 qCDebug(LOG_KTE) << "Starting new page," << lineCount << "lines up to now.";
201 paintNewPage(painter, currentPage, y, pl);
202 pageStarted = false;
203 painter.translate(dx: pl.xstart, dy: y);
204 }
205
206 const bool skipLine = m_dontPrintFoldedCode && !f.isLineVisible(line: lineCount);
207
208 if (!skipLine && m_printLineNumbers /*&& ! startCol*/) { // don't repeat!
209 paintLineNumber(painter, number: lineCount, pl);
210 }
211
212 if (!skipLine) {
213 paintLine(painter, line: lineCount, y, remainder, pl);
214 }
215
216 if (!remainder) {
217 lineCount++;
218 }
219 }
220
221 painter.end();
222}
223
224void PrintPainter::configure(const QPrinter *printer, PageLayout &pl) const
225{
226 pl.pageHeight = printer->height();
227 pl.pageWidth = printer->width();
228 pl.headerWidth = printer->width();
229 pl.innerMargin = m_useBox ? m_boxMargin : 6;
230 pl.maxWidth = printer->width();
231 pl.maxHeight = (m_useBox ? printer->height() - pl.innerMargin : printer->height());
232 pl.selectionOnly = (printer->printRange() == QPrinter::Selection);
233 pl.lastline = m_doc->lastLine();
234
235 if (m_view && pl.selectionOnly) {
236 // set a line range from the first selected line to the last
237 pl.selectionRange = m_view->selectionRange();
238 pl.firstline = pl.selectionRange.start().line();
239 pl.lastline = pl.selectionRange.end().line();
240 }
241
242 if (m_printLineNumbers) {
243 // a small space between the line numbers and the text
244 int _adj = m_renderer->currentFontMetrics().horizontalAdvance(QStringLiteral("5"));
245 // adjust available width and set horizontal start point for data
246 pl.maxWidth -= m_lineNumberWidth + _adj;
247 pl.xstart += m_lineNumberWidth + _adj;
248 }
249
250 if (m_useHeader || m_useFooter) {
251 // Set up a tag map
252 // This retrieves all tags, used or not, but
253 // none of these operations should be expensive,
254 // and searching each tag in the format strings is avoided.
255 QDateTime dt = QDateTime::currentDateTime();
256 std::map<QString, QString> tags;
257
258 KUser u(KUser::UseRealUserID);
259 tags[QStringLiteral("u")] = u.loginName();
260
261 tags[QStringLiteral("d")] = QLocale().toString(dateTime: dt, format: QLocale::ShortFormat);
262 tags[QStringLiteral("D")] = QLocale().toString(dateTime: dt, format: QLocale::LongFormat);
263 tags[QStringLiteral("h")] = QLocale().toString(time: dt.time(), format: QLocale::ShortFormat);
264 tags[QStringLiteral("y")] = QLocale().toString(date: dt.date(), format: QLocale::ShortFormat);
265 tags[QStringLiteral("Y")] = QLocale().toString(date: dt.date(), format: QLocale::LongFormat);
266 tags[QStringLiteral("f")] = m_doc->url().fileName();
267 tags[QStringLiteral("U")] = m_doc->url().toString();
268 if (pl.selectionOnly) {
269 QString s(i18n("(Selection of) "));
270 tags[QStringLiteral("f")].prepend(s);
271 tags[QStringLiteral("U")].prepend(s);
272 }
273
274 static const QRegularExpression reTags(QStringLiteral("%([dDfUhuyY])")); // TODO check for "%%<TAG>"
275
276 if (m_useHeader) {
277 pl.headerHeight = QFontMetrics(m_fhFont).height();
278 if (m_useBox || m_useHeaderBackground) {
279 pl.headerHeight += pl.innerMargin * 2;
280 } else {
281 pl.headerHeight += 1 + QFontMetrics(m_fhFont).leading();
282 }
283
284 pl.headerTagList = m_headerFormat;
285 QMutableStringListIterator it(pl.headerTagList);
286 while (it.hasNext()) {
287 QString tag = it.next();
288 QRegularExpressionMatch match;
289 int pos = tag.indexOf(re: reTags, from: 0, rmatch: &match);
290 QString rep;
291 while (pos > -1) {
292 rep = tags[match.captured(nth: 1)];
293 tag.replace(i: (uint)pos, len: 2, after: rep);
294 pos += rep.length();
295 pos = tag.indexOf(re: reTags, from: pos, rmatch: &match);
296 }
297 it.setValue(tag);
298 }
299 }
300
301 if (m_useFooter) {
302 pl.footerHeight = QFontMetrics(m_fhFont).height();
303 if (m_useBox || m_useFooterBackground) {
304 pl.footerHeight += 2 * pl.innerMargin;
305 } else {
306 pl.footerHeight += 1; // line only
307 }
308
309 pl.footerTagList = m_footerFormat;
310 QMutableStringListIterator it(pl.footerTagList);
311 while (it.hasNext()) {
312 QString tag = it.next();
313 QRegularExpressionMatch match;
314 int pos = tag.indexOf(re: reTags, from: 0, rmatch: &match);
315 QString rep;
316 while (pos > -1) {
317 rep = tags[match.captured(nth: 1)];
318 tag.replace(i: (uint)pos, len: 2, after: rep);
319 pos += rep.length();
320 pos = tag.indexOf(re: reTags, from: pos, rmatch: &match);
321 }
322 it.setValue(tag);
323 }
324
325 pl.maxHeight -= pl.footerHeight;
326 }
327 } // if ( useHeader || useFooter )
328
329 if (m_useBackground) {
330 if (!m_useBox) {
331 pl.xstart += pl.innerMargin;
332 pl.maxWidth -= pl.innerMargin * 2;
333 }
334 }
335
336 if (m_useBox) {
337 // set maxwidth to something sensible
338 pl.maxWidth -= (m_boxWidth + pl.innerMargin) * 2;
339 pl.xstart += m_boxWidth + pl.innerMargin;
340 // maxheight too..
341 pl.maxHeight -= m_boxWidth;
342 }
343
344 int pageHeight = pl.maxHeight;
345 if (m_useHeader) {
346 pageHeight -= pl.headerHeight + pl.innerMargin;
347 }
348 if (m_useFooter) {
349 pageHeight -= pl.footerHeight + pl.innerMargin;
350 }
351
352 const int linesPerPage = pageHeight / m_fontHeight;
353
354 if (printer->fromPage() > 0) {
355 pl.firstline = (printer->fromPage() - 1) * linesPerPage;
356 }
357
358 // now that we know the vertical amount of space needed,
359 // it is possible to calculate the total number of pages
360 // if needed, that is if any header/footer tag contains "%P".
361 if (!pl.headerTagList.filter(QStringLiteral("%P")).isEmpty() || !pl.footerTagList.filter(QStringLiteral("%P")).isEmpty()) {
362 qCDebug(LOG_KTE) << "'%P' found! calculating number of pages...";
363
364 // calculate total layouted lines in the document
365 int totalLines = 0;
366 // TODO: right now ignores selection printing
367 for (unsigned int i = pl.firstline; i <= pl.lastline; ++i) {
368 KateLineLayout rangeptr(*m_renderer);
369 rangeptr.setLine(line: i);
370 m_renderer->layoutLine(line: &rangeptr, maxwidth: (int)pl.maxWidth, cacheLayout: false);
371 totalLines += rangeptr.viewLineCount();
372 }
373
374 const int totalPages = (totalLines / linesPerPage) + ((totalLines % linesPerPage) > 0 ? 1 : 0);
375
376 // TODO: add space for guide if required
377 // if ( useGuide )
378 // _lt += (guideHeight + (fontHeight /2)) / fontHeight;
379
380 // substitute both tag lists
381 QString re(QStringLiteral("%P"));
382 QStringList::Iterator it;
383
384 for (it = pl.headerTagList.begin(); it != pl.headerTagList.end(); ++it) {
385 it->replace(before: re, after: QString::number(totalPages));
386 }
387
388 for (it = pl.footerTagList.begin(); it != pl.footerTagList.end(); ++it) {
389 (*it).replace(before: re, after: QString::number(totalPages));
390 }
391 }
392}
393
394void PrintPainter::paintNewPage(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const
395{
396 if (m_useHeader) {
397 paintHeader(painter, currentPage, y, pl);
398 }
399
400 if (m_useFooter) {
401 paintFooter(painter, currentPage, pl);
402 }
403
404 if (m_useBackground) {
405 paintBackground(painter, y, pl);
406 }
407
408 if (m_useBox) {
409 paintBox(painter, y, pl);
410 }
411
412 if (m_printGuide && currentPage == 1) {
413 paintGuide(painter, y, pl);
414 }
415}
416
417void PrintPainter::paintHeader(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const
418{
419 painter.save();
420 painter.setPen(QPen(m_headerForeground, 0.5));
421 painter.setFont(m_fhFont);
422
423 if (m_useHeaderBackground) {
424 painter.fillRect(x: 0, y: 0, w: pl.headerWidth, h: pl.headerHeight, b: m_headerBackground);
425 }
426
427 if (pl.headerTagList.count() == 3) {
428 int valign = (m_useBox || m_useHeaderBackground || m_useBackground) ? Qt::AlignVCenter : Qt::AlignTop;
429 int align = valign | Qt::AlignLeft;
430 int marg = (m_useBox || m_useHeaderBackground) ? pl.innerMargin : 0;
431 if (m_useBox) {
432 marg += m_boxWidth;
433 }
434
435 QString s;
436 for (int i = 0; i < 3; i++) {
437 s = pl.headerTagList[i];
438 if (s.indexOf(s: QLatin1String("%p")) != -1) {
439 s.replace(before: QLatin1String("%p"), after: QString::number(currentPage));
440 }
441
442 painter.drawText(x: marg, y: 0, w: pl.headerWidth - (marg * 2), h: pl.headerHeight, flags: align, str: s);
443 align = valign | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight);
444 }
445 }
446
447 if (!(m_useHeaderBackground || m_useBox || m_useBackground)) { // draw a 1 px (!?) line to separate header from contents
448 painter.drawLine(x1: 0, y1: pl.headerHeight - 1, x2: pl.headerWidth, y2: pl.headerHeight - 1);
449 // y += 1; now included in headerHeight
450 }
451
452 painter.restore();
453
454 y += pl.headerHeight + pl.innerMargin;
455}
456
457void PrintPainter::paintFooter(QPainter &painter, const uint currentPage, const PageLayout &pl) const
458{
459 painter.save();
460 painter.setPen(QPen(m_footerForeground, 0.5));
461 painter.setFont(m_fhFont);
462
463 if (!(m_useFooterBackground || m_useBox || m_useBackground)) { // draw a 1 px (!?) line to separate footer from contents
464 painter.drawLine(x1: 0, y1: pl.pageHeight - pl.footerHeight - 1, x2: pl.headerWidth, y2: pl.pageHeight - pl.footerHeight - 1);
465 }
466 if (m_useFooterBackground) {
467 painter.fillRect(x: 0, y: pl.pageHeight - pl.footerHeight, w: pl.headerWidth, h: pl.footerHeight, b: m_footerBackground);
468 }
469
470 if (pl.footerTagList.count() == 3) {
471 int align = Qt::AlignVCenter | Qt::AlignLeft;
472 int marg = (m_useBox || m_useFooterBackground) ? pl.innerMargin : 0;
473 if (m_useBox) {
474 marg += m_boxWidth;
475 }
476
477 QString s;
478 for (int i = 0; i < 3; i++) {
479 s = pl.footerTagList[i];
480 if (s.indexOf(s: QLatin1String("%p")) != -1) {
481 s.replace(before: QLatin1String("%p"), after: QString::number(currentPage));
482 }
483 painter.drawText(x: marg, y: pl.pageHeight - pl.footerHeight, w: pl.headerWidth - (marg * 2), h: pl.footerHeight, flags: align, str: s);
484 align = Qt::AlignVCenter | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight);
485 }
486 }
487 painter.restore();
488}
489
490void PrintPainter::paintGuide(QPainter &painter, uint &y, const PageLayout &pl) const
491{
492 // FIXME - this may span more pages...
493 // draw a box unless we have boxes, in which case we end with a box line
494 int _ystart = y;
495 QString _hlName = m_doc->highlight()->name();
496
497 // list of highlight attributes for the legend
498 const auto _attributes = m_doc->highlight()->attributesForDefinition(schema: m_view->rendererConfig()->schema());
499 const QColor _defaultPen = _attributes.at(i: 0)->foreground().color();
500
501 painter.save();
502 painter.setPen(_defaultPen);
503
504 int _marg = 0;
505 if (m_useBox) {
506 _marg += (2 * m_boxWidth) + (2 * pl.innerMargin);
507 } else {
508 if (m_useBackground) {
509 _marg += 2 * pl.innerMargin;
510 }
511 _marg += 1;
512 y += 1 + pl.innerMargin;
513 }
514
515 // draw a title string
516 QFont _titleFont = m_renderer->currentFont();
517 _titleFont.setBold(true);
518 painter.setFont(_titleFont);
519 QRect _r;
520 painter.drawText(r: QRect(_marg, y, pl.pageWidth - (2 * _marg), pl.maxHeight - y),
521 flags: Qt::AlignTop | Qt::AlignHCenter,
522 i18n("Typographical Conventions for %1", _hlName),
523 br: &_r);
524 const int _w = pl.pageWidth - (_marg * 2) - (pl.innerMargin * 2);
525 const int _x = _marg + pl.innerMargin;
526 y += _r.height() + pl.innerMargin;
527 painter.drawLine(x1: _x, y1: y, x2: _x + _w, y2: y);
528 y += 1 + pl.innerMargin;
529
530 int _widest(0);
531 for (const KTextEditor::Attribute::Ptr &attribute : std::as_const(t: _attributes)) {
532 const QString _name = attribute->name().section(asep: QLatin1Char(':'), astart: 1, aend: 1);
533 _widest = qMax(a: QFontMetrics(attribute->font()).boundingRect(text: _name).width(), b: _widest);
534 }
535
536 const int _guideCols = _w / (_widest + pl.innerMargin);
537
538 // draw attrib names using their styles
539 const int _cw = _w / _guideCols;
540 int _i = 0;
541
542 _titleFont.setUnderline(true);
543 QString _currentHlName;
544 for (const KTextEditor::Attribute::Ptr &attribute : std::as_const(t: _attributes)) {
545 QString _hl = attribute->name().section(asep: QLatin1Char(':'), astart: 0, aend: 0);
546 QString _name = attribute->name().section(asep: QLatin1Char(':'), astart: 1, aend: 1);
547 if (_hl != _hlName && _hl != _currentHlName) {
548 _currentHlName = _hl;
549 if (_i % _guideCols) {
550 y += m_fontHeight;
551 }
552 y += pl.innerMargin;
553 painter.setFont(_titleFont);
554 painter.setPen(_defaultPen);
555 painter.drawText(x: _x, y, w: _w, h: m_fontHeight, flags: Qt::AlignTop, str: _hl + QLatin1Char(' ') + i18n("text"));
556 y += m_fontHeight;
557 _i = 0;
558 }
559
560 painter.setPen(attribute->foreground().color());
561 painter.setFont(attribute->font());
562
563 if (attribute->hasProperty(propertyId: QTextFormat::BackgroundBrush)) {
564 QRect _rect = QFontMetrics(attribute->font()).boundingRect(text: _name);
565 _rect.moveTo(ax: _x + ((_i % _guideCols) * _cw), ay: y);
566 painter.fillRect(_rect, attribute->background());
567 }
568
569 painter.drawText(x: (_x + ((_i % _guideCols) * _cw)), y, w: _cw, h: m_fontHeight, flags: Qt::AlignTop, str: _name);
570
571 _i++;
572 if (_i && !(_i % _guideCols)) {
573 y += m_fontHeight;
574 }
575 }
576
577 if (_i % _guideCols) {
578 y += m_fontHeight; // last row not full
579 }
580 // draw a box around the legend
581 painter.setPen(_defaultPen);
582
583 if (m_useBox) {
584 painter.fillRect(x: 0, y: y + pl.innerMargin, w: pl.headerWidth, h: m_boxWidth, b: m_boxColor);
585 } else {
586 _marg -= 1;
587 painter.drawRect(x: _marg, y: _ystart, w: pl.pageWidth - (2 * _marg), h: y - _ystart + pl.innerMargin);
588 }
589
590 painter.restore();
591
592 y += (m_useBox ? m_boxWidth : 1) + (pl.innerMargin * 2);
593}
594
595void PrintPainter::paintBox(QPainter &painter, uint &y, const PageLayout &pl) const
596{
597 painter.save();
598 painter.setPen(QPen(m_boxColor, m_boxWidth));
599 painter.drawRect(x: 0, y: 0, w: pl.pageWidth, h: pl.pageHeight);
600
601 if (m_useHeader) {
602 painter.drawLine(x1: 0, y1: pl.headerHeight, x2: pl.headerWidth, y2: pl.headerHeight);
603 } else {
604 y += pl.innerMargin;
605 }
606
607 if (m_useFooter) { // drawline is not trustable, grr.
608 painter.fillRect(x: 0, y: pl.maxHeight + pl.innerMargin, w: pl.headerWidth, h: m_boxWidth, b: m_boxColor);
609 }
610
611 painter.restore();
612}
613
614void PrintPainter::paintBackground(QPainter &painter, const uint y, const PageLayout &pl) const
615{
616 // If we have a box, or the header/footer has backgrounds, we want to paint
617 // to the border of those. Otherwise just the contents area.
618 int _y = y;
619 int _h = pl.maxHeight - y;
620 if (m_useBox) {
621 _y -= pl.innerMargin;
622 _h += 2 * pl.innerMargin;
623 } else {
624 if (m_useHeaderBackground) {
625 _y -= pl.innerMargin;
626 _h += pl.innerMargin;
627 }
628 if (m_useFooterBackground) {
629 _h += pl.innerMargin;
630 }
631 }
632 painter.fillRect(x: 0, y: _y, w: pl.pageWidth, h: _h, b: m_view->rendererConfig()->backgroundColor());
633}
634
635void PrintPainter::paintLine(QPainter &painter, const uint line, uint &y, uint &remainder, const PageLayout &pl) const
636{
637 // HA! this is where we print [part of] a line ;]]
638 KateLineLayout rangeptr(*m_renderer);
639 rangeptr.setLine(line);
640 m_renderer->layoutLine(line: &rangeptr, maxwidth: (int)pl.maxWidth, cacheLayout: false);
641
642 // selectionOnly: clip non-selection parts and adjust painter position if needed
643 int _xadjust = 0;
644 if (pl.selectionOnly) {
645 if (m_view && m_view->blockSelection()) {
646 int _x = m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: 0), pos: pl.selectionRange.start());
647 int _x1 = m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: rangeptr.viewLineCount() - 1), pos: pl.selectionRange.end());
648 _xadjust = _x;
649 painter.translate(dx: -_xadjust, dy: 0);
650 painter.setClipRegion(QRegion(_x, 0, _x1 - _x, rangeptr.viewLineCount() * m_fontHeight));
651
652 } else if (line == pl.firstline || line == pl.lastline) {
653 QRegion region(0, 0, pl.maxWidth, rangeptr.viewLineCount() * m_fontHeight);
654
655 if (line == pl.firstline) {
656 region = region.subtracted(r: QRegion(0, 0, m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: 0), pos: pl.selectionRange.start()), m_fontHeight));
657 }
658
659 if (line == pl.lastline) {
660 int _x = m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: rangeptr.viewLineCount() - 1), pos: pl.selectionRange.end());
661 region = region.subtracted(r: QRegion(_x, 0, pl.maxWidth - _x, m_fontHeight));
662 }
663
664 painter.setClipRegion(region);
665 }
666 }
667
668 // If the line is too long (too many 'viewlines') to fit the remaining vertical space,
669 // clip and adjust the painter position as necessary
670 int _lines = rangeptr.viewLineCount(); // number of "sublines" to paint.
671
672 int proceedLines = _lines;
673 if (remainder) {
674 proceedLines = qMin(a: (pl.maxHeight - y) / m_fontHeight, b: remainder);
675
676 painter.translate(dx: 0, dy: -(_lines - int(remainder)) * m_fontHeight + 1);
677 painter.setClipRect(x: 0,
678 y: (_lines - int(remainder)) * m_fontHeight + 1,
679 w: pl.maxWidth,
680 h: proceedLines * m_fontHeight); // ### drop the crosspatch in printerfriendly mode???
681 remainder -= proceedLines;
682 } else if (y + m_fontHeight * _lines > pl.maxHeight) {
683 remainder = _lines - ((pl.maxHeight - y) / m_fontHeight);
684 painter.setClipRect(x: 0, y: 0, w: pl.maxWidth, h: (_lines - int(remainder)) * m_fontHeight + 1); // ### drop the crosspatch in printerfriendly mode???
685 } else if (!pl.selectionOnly) {
686 painter.setClipRegion(QRegion());
687 painter.setClipping(false);
688 }
689
690 KateRenderer::PaintTextLineFlags flags;
691 if (!m_dontPrintFoldedCode) {
692 flags.setFlag(flag: KateRenderer::PaintTextLineFlag::SkipDrawFirstInvisibleLineUnderlined);
693 }
694
695 m_renderer->paintTextLine(paint&: painter, range: &rangeptr, xStart: 0, xEnd: (int)pl.maxWidth, textClipRect: QRectF{}, cursor: nullptr, flags);
696
697 painter.setClipping(false);
698 painter.translate(dx: _xadjust, dy: (m_fontHeight * (_lines - remainder)));
699
700 y += m_fontHeight * proceedLines;
701}
702
703void PrintPainter::paintLineNumber(QPainter &painter, const uint number, const PageLayout &pl) const
704{
705 const int left = ((m_useBox || m_useBackground) ? pl.innerMargin : 0) - pl.xstart;
706
707 painter.save();
708 painter.setFont(m_renderer->currentFont());
709 painter.setPen(m_view->rendererConfig()->lineNumberColor());
710 painter.drawText(x: left, y: 0, w: m_lineNumberWidth, h: m_fontHeight, flags: Qt::AlignRight | Qt::AlignVCenter, str: QString::number(number + 1));
711 painter.restore();
712}
713
714// END PrintPainter
715

source code of ktexteditor/src/printing/printpainter.cpp