First drawing in Cairo with Pango
parent
4d00c7bf05
commit
45583564b9
|
@ -56,9 +56,9 @@ The current plan:
|
|||
|
||||
Decentralized Browser written in C++20 with C libraries. And using the [cmark-gfm](https://github.com/github/cmark-gfm) library, used for CommonMark (markdown) parsing.
|
||||
|
||||
The GUI toolkit and 2D/vector graphics engine is not yet decided (see research below).
|
||||
Browser is using GTK as UI library including Pango & Cairo for text drawing and manipulation.
|
||||
|
||||
We can also still change the language of the source code (iso markdown). Atleast no HTML and JavaScript anymore, content is king after all.
|
||||
For now we will use markdown as the source of the site. No HTML and JavaScript anymore, content is king after all.
|
||||
|
||||
### Development Environment
|
||||
|
||||
|
@ -70,7 +70,8 @@ For the build you need at least:
|
|||
|
||||
* GCC 9 or higher (GCC 8 should also work)
|
||||
* CMake
|
||||
* Qt (`qt5-default` package) -> for now
|
||||
* GTK & Cairo & Pango (including C++ bindings):
|
||||
- Install: `libgtkmm-3.0-dev` under Debian based distros
|
||||
|
||||
For Release packaging:
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
FROM danger89/cmake:3.1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y qt5-default \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
@ -1,6 +1,6 @@
|
|||
[Desktop Entry]
|
||||
Name=Browser
|
||||
Comment=Decentralized WWWW
|
||||
Comment=Decentralized WWW
|
||||
Exec=/usr/bin/browser
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
|
|
@ -21,7 +21,7 @@ find_package(Threads REQUIRED)
|
|||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTKMM gtkmm-3.0)
|
||||
pkg_check_modules(CAIRO REQUIRED cairo)
|
||||
pkg_check_modules(CAIRO cairomm-1.0)
|
||||
|
||||
add_definitions(${GTKMM_CFLAGS_OTHER})
|
||||
|
||||
|
|
|
@ -21,8 +21,7 @@ MainWindow::MainWindow()
|
|||
m_scrolledWindow.add(m_renderArea);
|
||||
m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
m_renderArea.show();
|
||||
m_scrolledWindow.show();
|
||||
show_all_children();
|
||||
|
||||
// Setup parser
|
||||
setupParser();
|
||||
|
@ -43,9 +42,8 @@ void MainWindow::setupParser()
|
|||
|
||||
cmark_node *root_node = parser->parseFile(filePath);
|
||||
if (root_node != NULL) {
|
||||
// For HTML: parser->renderHTML(root_node);
|
||||
// Render AST to drawing area
|
||||
m_renderArea.renderDocument(root_node);
|
||||
// process AST, which can then be drawed on render/drawing area
|
||||
m_renderArea.processDocument(root_node);
|
||||
cmark_node_free(root_node);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#include "render-area.h"
|
||||
|
||||
#include "node.h"
|
||||
|
||||
#define PANGO_SCALE_XXX_LARGE ((double)1.98)
|
||||
|
||||
RenderArea::RenderArea()
|
||||
: currentX(0),
|
||||
currentY(0),
|
||||
|
@ -14,32 +15,73 @@ RenderArea::RenderArea()
|
|||
paragraphHeightOffset(5),
|
||||
headingHeightOffset(10),
|
||||
listXOffset(15),
|
||||
bulletPointDynamicOffset(0)
|
||||
bulletPointDynamicOffset(0),
|
||||
isBold(false),
|
||||
isItalic(false),
|
||||
fontSize(10),
|
||||
fontFamily("Ubuntu")
|
||||
{
|
||||
// Resize the drawing area to get scroll bars
|
||||
set_size_request(800, 1000);
|
||||
|
||||
createPangoContexts();
|
||||
}
|
||||
|
||||
RenderArea::~RenderArea()
|
||||
{
|
||||
}
|
||||
|
||||
void RenderArea::renderDocument(cmark_node *root_node)
|
||||
void RenderArea::createPangoContexts()
|
||||
{
|
||||
defaultFont.set_family(fontFamily);
|
||||
defaultFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM);
|
||||
|
||||
boldFont.set_family(fontFamily);
|
||||
boldFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM);
|
||||
boldFont.set_weight(Pango::WEIGHT_BOLD);
|
||||
|
||||
italicFont.set_family(fontFamily);
|
||||
italicFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM);
|
||||
italicFont.set_style(Pango::Style::STYLE_ITALIC);
|
||||
|
||||
boldItalicFont.set_family(fontFamily);
|
||||
boldItalicFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM);
|
||||
boldItalicFont.set_weight(Pango::WEIGHT_BOLD);
|
||||
boldItalicFont.set_style(Pango::Style::STYLE_ITALIC);
|
||||
|
||||
heading1Font.set_family(fontFamily);
|
||||
heading1Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XXX_LARGE);
|
||||
heading1Font.set_weight(Pango::WEIGHT_BOLD);
|
||||
|
||||
heading2Font.set_family(fontFamily);
|
||||
heading2Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XX_LARGE);
|
||||
heading2Font.set_weight(Pango::WEIGHT_BOLD);
|
||||
|
||||
heading3Font.set_family(fontFamily);
|
||||
heading3Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_X_LARGE);
|
||||
heading3Font.set_weight(Pango::WEIGHT_BOLD);
|
||||
|
||||
heading4Font.set_family(fontFamily);
|
||||
heading4Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_LARGE);
|
||||
heading4Font.set_weight(Pango::WEIGHT_BOLD);
|
||||
}
|
||||
|
||||
void RenderArea::processDocument(cmark_node *root_node)
|
||||
{
|
||||
textList.clear(); // reset
|
||||
|
||||
cmark_event_type ev_type;
|
||||
cmark_iter *iter = cmark_iter_new(root_node);
|
||||
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
cmark_node *cur = cmark_iter_get_node(iter);
|
||||
renderNode(cur, ev_type);
|
||||
processNode(cur, ev_type);
|
||||
}
|
||||
// TODO: call the draw text/etc. methods below.
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the locations, render and paint the content/objects
|
||||
* to a QGraphicsScene
|
||||
* Calculates the drawing locations and parse each node in the AST
|
||||
*/
|
||||
void RenderArea::renderNode(cmark_node *node, cmark_event_type ev_type)
|
||||
void RenderArea::processNode(cmark_node *node, cmark_event_type ev_type)
|
||||
{
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
|
||||
|
@ -138,15 +180,61 @@ void RenderArea::renderNode(cmark_node *node, cmark_event_type ev_type)
|
|||
break;
|
||||
|
||||
case CMARK_NODE_TEXT: {
|
||||
printf("Text: %s\n", cmark_node_get_literal(node));
|
||||
/*const QRectF rec = drawText(cmark_node_get_literal(node));
|
||||
// Skip paragraph if listing is enabled
|
||||
if (listLevel == 0) {
|
||||
currentX += rec.width();
|
||||
// Instead of creating seperate pango layouts we may want to use Pango attributes,
|
||||
// for changing parts of the text. Which is most likely be faster.
|
||||
// https://developer.gnome.org/pango/stable/pango-Text-Attributes.html
|
||||
// Pango is using a simple parser for parsing (X)HTML:
|
||||
// https://developer.gnome.org/glib/stable/glib-Simple-XML-Subset-Parser.html
|
||||
// We can use simular parse functions and using their own 'OpenTag' struct containing a list of Pango attributes:
|
||||
// https://gitlab.gnome.org/GNOME/pango/-/blob/master/pango/pango-markup.c#L515
|
||||
|
||||
// For some reason Pango::Layout:create objects doesn't show up in cairo content
|
||||
auto layout = create_pango_layout(cmark_node_get_literal(node));
|
||||
if (headingLevel > 0) {
|
||||
switch (headingLevel)
|
||||
{
|
||||
case 1:
|
||||
layout->set_font_description(heading1Font);
|
||||
break;
|
||||
case 2:
|
||||
layout->set_font_description(heading2Font);
|
||||
break;
|
||||
case 3:
|
||||
layout->set_font_description(heading3Font);
|
||||
break;
|
||||
case 4:
|
||||
layout->set_font_description(heading4Font);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (rec.height() > heighestHigh)
|
||||
heighestHigh = rec.height();
|
||||
*/
|
||||
} else if (isBold && isItalic) {
|
||||
layout->set_font_description(boldItalicFont);
|
||||
} else if (isBold) {
|
||||
layout->set_font_description(boldFont);
|
||||
} else if (isItalic) {
|
||||
layout->set_font_description(italicFont);
|
||||
} else {
|
||||
layout->set_font_description(defaultFont);
|
||||
}
|
||||
//layout->set_width(100);
|
||||
int textWidth;
|
||||
int textHeight;
|
||||
layout->get_pixel_size(textWidth, textHeight);
|
||||
|
||||
// Add text to list
|
||||
text textStruct;
|
||||
textStruct.x = currentX;
|
||||
textStruct.y = currentY;
|
||||
textStruct.layout = layout;
|
||||
textList.push_back(textStruct);
|
||||
|
||||
if (textHeight > heighestHigh)
|
||||
heighestHigh = textHeight;
|
||||
// Skip paragraph if listing is enabled,
|
||||
// in all other cases increase x with text width
|
||||
if (listLevel == 0)
|
||||
currentX += textWidth;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -176,11 +264,11 @@ void RenderArea::renderNode(cmark_node *node, cmark_event_type ev_type)
|
|||
break;
|
||||
|
||||
case CMARK_NODE_STRONG:
|
||||
//bold = entering;
|
||||
isBold = entering;
|
||||
break;
|
||||
|
||||
case CMARK_NODE_EMPH:
|
||||
//italic = entering;
|
||||
isItalic = entering;
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINK:
|
||||
|
@ -200,82 +288,27 @@ void RenderArea::renderNode(cmark_node *node, cmark_event_type ev_type)
|
|||
}
|
||||
}
|
||||
|
||||
// Overrided method of GTK DrawingArea
|
||||
bool RenderArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
|
||||
{
|
||||
Gtk::Allocation allocation = get_allocation();
|
||||
// Total drawing area size
|
||||
const int width = allocation.get_width();
|
||||
const int height = allocation.get_height();
|
||||
|
||||
const int rectangle_width = width;
|
||||
const int rectangle_height = height / 2;
|
||||
|
||||
// Draw a black rectangle
|
||||
cr->set_source_rgb(0.0, 0.0, 0.0);
|
||||
draw_rectangle(cr, rectangle_width, rectangle_height);
|
||||
|
||||
// and some white text
|
||||
// White background
|
||||
cr->set_source_rgb(1.0, 1.0, 1.0);
|
||||
draw_text(cr, rectangle_width, rectangle_height);
|
||||
|
||||
// flip the image vertically
|
||||
// see http://www.cairographics.org/documentation/cairomm/reference/classCairo_1_1Matrix.html
|
||||
// the -1 corresponds to the yy part (the flipping part)
|
||||
// the height part is a translation (we could have just called cr->translate(0, height) instead)
|
||||
// it's height and not height / 2, since we want this to be on the second part of our drawing
|
||||
// (otherwise, it would draw over the previous part)
|
||||
Cairo::Matrix matrix(1.0, 0.0, 0.0, -1.0, 0.0, height);
|
||||
|
||||
// apply the matrix
|
||||
cr->transform(matrix);
|
||||
|
||||
// white rectangle
|
||||
cr->set_source_rgb(1.0, 1.0, 1.0);
|
||||
draw_rectangle(cr, rectangle_width, rectangle_height);
|
||||
|
||||
// black text
|
||||
cr->set_source_rgb(0.0, 0.0, 0.0);
|
||||
draw_text(cr, rectangle_width, rectangle_height);
|
||||
|
||||
|
||||
draw_text(cr, 300, 110);
|
||||
draw_text(cr, 200, 150);
|
||||
draw_text(cr, 100, 240);
|
||||
draw_text(cr, 400, 280);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderArea::draw_rectangle(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||
int width, int height)
|
||||
{
|
||||
cr->rectangle(0, 0, width, height);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
void RenderArea::draw_text(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||
int rectangle_width, int rectangle_height)
|
||||
{
|
||||
// http://developer.gnome.org/pangomm/unstable/classPango_1_1FontDescription.html
|
||||
Pango::FontDescription font;
|
||||
|
||||
font.set_family("Ubuntu");
|
||||
font.set_weight(Pango::WEIGHT_BOLD);
|
||||
if (rectangle_width < 150)
|
||||
font.set_style(Pango::Style::STYLE_ITALIC);
|
||||
|
||||
// http://developer.gnome.org/pangomm/unstable/classPango_1_1Layout.html
|
||||
auto layout = create_pango_layout("Hi there!");
|
||||
|
||||
layout->set_font_description(font);
|
||||
|
||||
int text_width;
|
||||
int text_height;
|
||||
|
||||
//get the text dimensions (it updates the variables -- by reference)
|
||||
layout->get_pixel_size(text_width, text_height);
|
||||
|
||||
// Position the text in the middle
|
||||
cr->move_to((rectangle_width-text_width)/2, (rectangle_height-text_height)/2);
|
||||
|
||||
layout->show_in_cairo_context(cr);
|
||||
|
||||
// Set to black for text
|
||||
cr->set_source_rgb(0.0, 0.0, 0.0);
|
||||
|
||||
std::list<text>::iterator it;
|
||||
for(it = textList.begin(); it != textList.end(); ++it) {
|
||||
auto text = (*it);
|
||||
cr->move_to(text.x, text.y);
|
||||
text.layout->show_in_cairo_context(cr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,18 +2,27 @@
|
|||
#define RENDER_AREA_H
|
||||
|
||||
#include <gtkmm/drawingarea.h>
|
||||
#include <pangomm/layout.h>
|
||||
#include <cmark-gfm.h>
|
||||
|
||||
struct text {
|
||||
int x;
|
||||
int y;
|
||||
Glib::RefPtr<Pango::Layout> layout;
|
||||
};
|
||||
|
||||
class RenderArea : public Gtk::DrawingArea
|
||||
{
|
||||
public:
|
||||
RenderArea();
|
||||
virtual ~RenderArea();
|
||||
|
||||
void renderDocument(cmark_node *root_node);
|
||||
void processDocument(cmark_node *root_node);
|
||||
|
||||
protected:
|
||||
//Override default signal handler:
|
||||
std::list<text> textList;
|
||||
|
||||
// Override default signal handler:
|
||||
bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
|
||||
|
||||
private:
|
||||
|
@ -29,10 +38,22 @@ private:
|
|||
int headingHeightOffset;
|
||||
int listXOffset;
|
||||
int bulletPointDynamicOffset;
|
||||
bool isBold;
|
||||
bool isItalic;
|
||||
int fontSize;
|
||||
std::string fontFamily;
|
||||
|
||||
void renderNode(cmark_node *node, cmark_event_type ev_type);
|
||||
void draw_rectangle(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
|
||||
void draw_text(const Cairo::RefPtr<Cairo::Context>& cr, int rectangle_width, int rectangle_height);
|
||||
Pango::FontDescription defaultFont;
|
||||
Pango::FontDescription boldFont;
|
||||
Pango::FontDescription italicFont;
|
||||
Pango::FontDescription boldItalicFont;
|
||||
Pango::FontDescription heading1Font;
|
||||
Pango::FontDescription heading2Font;
|
||||
Pango::FontDescription heading3Font;
|
||||
Pango::FontDescription heading4Font;
|
||||
|
||||
void createPangoContexts();
|
||||
void processNode(cmark_node *node, cmark_event_type ev_type);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,3 +1,3 @@
|
|||
missingIncludeSystem
|
||||
missingInclude:lib/*
|
||||
unusedFunction:src/render-area.cc:203
|
||||
unusedFunction:src/render-area.cc:292
|
||||
|
|
Loading…
Reference in New Issue