html/css: auto split a long UTF-8 text to next line, Chinese (中文) ok master
authoryu.dongliang <18588496441@163.com>
Thu, 30 Apr 2026 08:57:37 +0000 (16:57 +0800)
committeryu.dongliang <18588496441@163.com>
Thu, 30 Apr 2026 08:57:37 +0000 (16:57 +0800)
examples/p_multi_line.html [new file with mode: 0644]
examples/table.html
html/abc_html.c
html/abc_io_http.c
html/abc_io_str.c
html/abc_obj.h
ui/abc_layout_h1.c
ui/abc_layout_td.c
ui/abc_render_h1.c

diff --git a/examples/p_multi_line.html b/examples/p_multi_line.html
new file mode 100644 (file)
index 0000000..b90bf63
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+p
+{
+       border:1px solid red;
+       background-color:green;
+       color:white;
+}
+</style>
+</head>
+
+<body>
+<p>A drawing operator that generates the shape from a string of UTF-8 characters, rendered according to the current font_face, font_size (font_matrix), and font_options.This function first computes a set of glyphs for the string of text. The first glyph is placed so that its origin is at the current point. The origin of each subsequent glyph is offset from that of the previous glyph by the advance values of the previous glyph.</p>
+<p>一旦为页面设置了恰当的 DTD,大多数浏览器都会按照上面的图示来呈现内容。然而 IE 5 和 6 的呈现却是不正确的。根据 W3C 的规范,元素内容占据的空间是由 width 属性设置的,而内容周围的 padding 和 border 值是另外计算的。不幸的是,IE5.X 和 6 在怪异模式中使用自己的非标准模型。这些浏览器的 width 属性不是内容的宽度,而是内容、内边距和边框的宽度的总和。</p>
+</body>
+</html>
index 15b3ba14416b0cfda9de55f100f65fce7475390b..7f2474b1a7e7a89c9f0ecd5cb118b2cd5946cfe1 100644 (file)
@@ -7,11 +7,7 @@ table,th,td
        border:1px solid red;
 }
 
        border:1px solid red;
 }
 
-table
-{
-       width:100%;
-/*     border-collapse:collapse;*/
-}
+table {width:100%;}
 
 th
 {
 
 th
 {
index af65462763b39d47b4191f7d42661a0205f14a88..ebfdcb73c0e843025479284c28a10f3e5d96cc61 100644 (file)
@@ -263,6 +263,9 @@ static html_attr_t  p_attrs[] =
        {border_keys,      "",         ABC_HTML_ATTR_BORDER,          ABC_HTML_FLAG_SHOW},
        {padding_keys,     "",         ABC_HTML_ATTR_PADDING,         ABC_HTML_FLAG_SHOW},
 
        {border_keys,      "",         ABC_HTML_ATTR_BORDER,          ABC_HTML_FLAG_SHOW},
        {padding_keys,     "",         ABC_HTML_ATTR_PADDING,         ABC_HTML_FLAG_SHOW},
 
+       {width_keys,       "",         ABC_HTML_ATTR_WIDTH,           ABC_HTML_FLAG_SHOW},
+       {height_keys,      "",         ABC_HTML_ATTR_HEIGHT,          ABC_HTML_FLAG_SHOW},
+
        {class_keys,       "",         ABC_HTML_ATTR_CLASS,           ABC_HTML_FLAG_SHOW},
        {style_keys,       "",         ABC_HTML_ATTR_STYLE,           ABC_HTML_FLAG_SHOW},
        {id_keys,          "",         ABC_HTML_ATTR_ID,              ABC_HTML_FLAG_SHOW},
        {class_keys,       "",         ABC_HTML_ATTR_CLASS,           ABC_HTML_FLAG_SHOW},
        {style_keys,       "",         ABC_HTML_ATTR_STYLE,           ABC_HTML_FLAG_SHOW},
        {id_keys,          "",         ABC_HTML_ATTR_ID,              ABC_HTML_FLAG_SHOW},
index 18a232c4885a1db0c4bfb78784fb60685fe1464f..992bfed5af493ccd5e6a6df9eaa340a648b356b9 100644 (file)
@@ -491,7 +491,7 @@ static int __http_popc(abc_io_t* io)
                        assert(http->rpos > 0);
 
                        if (http->rpos < http->rbuf->len)
                        assert(http->rpos > 0);
 
                        if (http->rpos < http->rbuf->len)
-                               c = http->rbuf->data[http->rpos++];
+                               c = (uint8_t)http->rbuf->data[http->rpos++];
                        else
                                c = EOF;
                        pthread_mutex_unlock(&http->mutex);
                        else
                                c = EOF;
                        pthread_mutex_unlock(&http->mutex);
index ba31fcf14289556f46548d7e5cab44f47a8f15b0..8dedd95d3c5205124ad4bc9f97d546806026e556 100644 (file)
@@ -47,8 +47,10 @@ static int __str_popc(abc_io_t* io)
        if (io && io->priv) {
                s = io->priv;
 
        if (io && io->priv) {
                s = io->priv;
 
-               if (s->text_i < s->text->len)
-                       return s->text->data[s->text_i++];
+               if (s->text_i < s->text->len) {
+                       uint8_t c = s->text->data[s->text_i++];
+                       return c;
+               }
        }
 
        return EOF;
        }
 
        return EOF;
index ee84845be70e4871ae03eb30c33ae39319591a86..9c639e22892a31023022ed52e7b81e1b9b947a46 100644 (file)
@@ -7,6 +7,7 @@
 #include"abc_io.h"
 
 typedef struct abc_obj_s   abc_obj_t;
 #include"abc_io.h"
 
 typedef struct abc_obj_s   abc_obj_t;
+typedef struct abc_text_s  abc_text_t;
 
 enum abc_objs
 {
 
 enum abc_objs
 {
@@ -151,6 +152,22 @@ enum abc_border_style
        ABC_BORDER_DASHED,
 };
 
        ABC_BORDER_DASHED,
 };
 
+enum abc_line_style
+{
+       ABC_LINE_NONE,
+       ABC_LINE_UNDER,
+       ABC_LINE_OVER,
+       ABC_LINE_THROUGH,
+};
+
+struct abc_text_s
+{
+       abc_text_t*     next;
+
+       int             start;
+       int             len;
+};
+
 struct abc_obj_s
 {
        scf_list_t      list;
 struct abc_obj_s
 {
        scf_list_t      list;
@@ -192,6 +209,7 @@ struct abc_obj_s
        char**          keys;
        scf_string_t*   value;
        scf_string_t*   text;
        char**          keys;
        scf_string_t*   value;
        scf_string_t*   text;
+       abc_text_t*     text_splits; // for layout, split a long text to multi-lines and save every-line here
 
        int             text_line;
        int             text_pos;
 
        int             text_line;
        int             text_pos;
index 2e869abee78a419713ee0b6b5ad44ab8325b263b..c735cdc1b09c11cafac2c9cc52fba30db9a25192 100644 (file)
@@ -1,5 +1,116 @@
 #include"abc.h"
 
 #include"abc.h"
 
+static int __layout_text_split(cairo_text_extents_t* extents, cairo_t* cr, const uint8_t* text, int len)
+{
+       int i;
+       for (i = len; i > 0; i--) {
+               int c = text[i];
+
+               if ('\0' == c || ' ' == c || '\t' == c || '\r' == c || '\n' == c || '-' == c)
+                       break;
+               else if (c < 0x80)
+                       continue;
+
+               if ((c >> 6) != 0x2)
+                       break;
+       }
+
+       if (i > 0)
+               len = i;
+
+       scf_string_t* s = scf_string_cstr_len(text, len);
+       if (!s)
+               return -ENOMEM;
+       cairo_text_extents(cr, s->data, extents);
+
+       scf_string_free(s);
+       return len;
+}
+
+int __layout_text(cairo_text_extents_t* extents, cairo_t* cr, abc_obj_t* obj, int width)
+{
+       abc_obj_t*  attr;
+       abc_obj_t*  style;
+
+       attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT);
+       if (attr) {
+               int bold   = CAIRO_FONT_WEIGHT_NORMAL;
+               int italic = CAIRO_FONT_SLANT_NORMAL;
+
+               style = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_STYLE);
+               if (style) {
+                       if (!__html_strcmp(style->value->data, "bold"))
+                               bold = CAIRO_FONT_WEIGHT_BOLD;
+
+                       else if (!__html_strcmp(style->value->data, "italic") || !__html_strcmp(style->value->data, "oblique"))
+                               italic = CAIRO_FONT_SLANT_OBLIQUE;
+               }
+
+               cairo_select_font_face(cr, attr->value->data, italic, bold);
+       }
+
+       int size = 16;
+       attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_SIZE);
+       if (attr)
+               size = atoi(attr->value->data);
+
+       cairo_set_font_size(cr, size);
+       cairo_text_extents (cr, obj->text->data, extents);
+
+       int w  = extents->x_bearing + extents->width;
+       if (w <= width)
+               return 0;
+
+       extents->width  = width;
+       extents->height = 0;
+
+       if (obj->text_splits)
+               scf_slist_clear(obj->text_splits, abc_text_t, next, free);
+
+       assert(NULL    == obj->text_splits);
+       abc_text_t** h = &obj->text_splits;
+
+       int n_lines = w / width;
+       int n_chars = obj->text->len * 5 / 4 / n_lines;
+       int n;
+       int i;
+
+       for (i = 0; i < obj->text->len; ) {
+               n  = n_chars;
+
+               if (n > obj->text->len - i)
+                       n = obj->text->len - i;
+
+               while (1) {
+                       cairo_text_extents_t  tmp;
+
+                       n = __layout_text_split(&tmp, cr, obj->text->data + i, n);
+                       if (n < 0)
+                               return n;
+
+                       if (tmp.x_bearing + tmp.width <= width)
+                       {
+                               abc_text_t* t = calloc(1, sizeof(abc_text_t));
+                               if (!t)
+                                       return -ENOMEM;
+                               t->start = i;
+                               t->len   = n;
+
+                               *h = t;
+                               h  = &t->next;
+                               i += n;
+
+                               extents->height += tmp.height;
+                               break;
+                       }
+
+                       n--;
+               }
+       }
+
+       return 0;
+}
+
 int abc_layout_h1(abc_layout_t* layout, abc_obj_t* obj, int width, int height)
 {
        abc_obj_t*            attr;
 int abc_layout_h1(abc_layout_t* layout, abc_obj_t* obj, int width, int height)
 {
        abc_obj_t*            attr;
@@ -34,21 +145,15 @@ int abc_layout_h1(abc_layout_t* layout, abc_obj_t* obj, int width, int height)
 
        int size = 16;
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_SIZE);
 
        int size = 16;
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_SIZE);
-       if (attr) {
+       if (attr)
                size = atoi(attr->value->data);
                size = atoi(attr->value->data);
-               cairo_set_font_size(cr, size);
-       }
-
-       cairo_font_options_t *options = cairo_font_options_create();
-       cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_SUBPIXEL);
-       cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_FULL);
-       cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_ON);
-       cairo_set_font_options(cr, options);
-       cairo_font_options_destroy(options);
 
 
+       cairo_set_font_size  (cr, size);
        cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
 
        cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
 
-       cairo_text_extents(cr, obj->text->data, &extents);
+       int ret = __layout_text(&extents, cr, obj, width);
+       if (ret < 0)
+               scf_loge("ret: %d\n", ret);
 
        int w = 0;
        if (ABC_HTML_OL == obj->parent->type)
 
        int w = 0;
        if (ABC_HTML_OL == obj->parent->type)
@@ -80,7 +185,7 @@ int abc_layout_h1(abc_layout_t* layout, abc_obj_t* obj, int width, int height)
        obj->w = (obj->w + 3) & ~0x3;
        obj->h = (obj->h + 3) & ~0x3;
 
        obj->w = (obj->w + 3) & ~0x3;
        obj->h = (obj->h + 3) & ~0x3;
 
-       scf_logd("%s, w: %d, h: %d, x_bearing: %lg, y_bearing: %lg, width: %lg, height: %lg, x_advance: %lg, y_advance: %lg\n",
+       scf_logi("%s, w: %d, h: %d, x_bearing: %lg, y_bearing: %lg, width: %lg, height: %lg, x_advance: %lg, y_advance: %lg\n",
                        obj->text->data, obj->w, obj->h,
                        extents.x_bearing, extents.y_bearing, extents.width, extents.height,
                        extents.x_advance, extents.y_advance);
                        obj->text->data, obj->w, obj->h,
                        extents.x_bearing, extents.y_bearing, extents.width, extents.height,
                        extents.x_advance, extents.y_advance);
index fac5fff9f6b8f30388aa0ae056b1241d10f5fd7d..82327ff9d1b2b68b12cf49f29bee87c5e5f95bef 100644 (file)
@@ -37,7 +37,7 @@ int abc_layout_td(abc_layout_t* layout, abc_obj_t* obj, int width, int height)
        while (table && ABC_HTML_TABLE != table->type)
                table = table->parent;
 
        while (table && ABC_HTML_TABLE != table->type)
                table = table->parent;
 
-       // width of event table element is width of the max one
+       // width of every table element is width of the max one
        if (obj->w < table->td_width)
                obj->w = table->td_width;
        else
        if (obj->w < table->td_width)
                obj->w = table->td_width;
        else
index 461f2596b8e7e381c51f2e49455d2cfa1ff78765..d4b6a50ddc34614641abee934750e7234bf2109e 100644 (file)
@@ -38,6 +38,42 @@ static int _render_fini_h1(abc_render_t* render)
        return 0;
 }
 
        return 0;
 }
 
+void __draw_text(cairo_text_extents_t* extents, cairo_t* cr, const char* text, double x, double y, int line_type, int line_width)
+{
+       cairo_text_extents(cr, text, extents);
+       double w = extents->width;
+
+       x += extents->x_bearing;
+       y -= extents->y_bearing;
+
+       cairo_set_line_width(cr, 1);
+       cairo_move_to(cr, x, y);
+       cairo_show_text(cr, text);
+       cairo_stroke(cr);
+
+       cairo_set_line_width(cr, line_width);
+       switch (line_type)
+       {
+               case ABC_LINE_UNDER:
+                       cairo_move_to(cr, x, y + line_width + 1.0);
+                       cairo_line_to(cr, w, y + line_width + 1.0);
+                       break;
+
+               case ABC_LINE_OVER:
+                       cairo_move_to(cr, x, 1.0);
+                       cairo_line_to(cr, w, 1.0);
+                       break;
+
+               case ABC_LINE_THROUGH:
+                       cairo_move_to(cr, x, y + extents->y_bearing / 2.0 + line_width);
+                       cairo_line_to(cr, w, y + extents->y_bearing / 2.0 + line_width);
+                       break;
+               default:
+                       break;
+       };
+       cairo_stroke(cr);
+}
+
 int __init_text(cairo_t* cr, abc_obj_t* obj, int num_flag)
 {
        abc_obj_t* attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT);
 int __init_text(cairo_t* cr, abc_obj_t* obj, int num_flag)
 {
        abc_obj_t* attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT);
@@ -64,59 +100,68 @@ int __init_text(cairo_t* cr, abc_obj_t* obj, int num_flag)
        double size = 16.0;
 
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_SIZE);
        double size = 16.0;
 
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_SIZE);
-       if (attr) {
+       if (attr)
                size = atoi(attr->value->data);
                size = atoi(attr->value->data);
-               cairo_set_font_size(cr, size);
-       }
-
-       cairo_font_options_t *options = cairo_font_options_create();
-       cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_SUBPIXEL);
-       cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_FULL);
-       cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_ON);
-       cairo_set_font_options(cr, options);
-       cairo_font_options_destroy(options);
 
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_COLOR);
        if (attr)
                abc_css_color(&r, &g, &b, attr->value->data);
 
 
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_FONT_COLOR);
        if (attr)
                abc_css_color(&r, &g, &b, attr->value->data);
 
+       cairo_set_font_size  (cr, size);
        cairo_set_source_rgba(cr, r, g, b, 1.0);
        cairo_set_source_rgba(cr, r, g, b, 1.0);
-       cairo_text_extents   (cr, obj->text->data, &extents);
 
 
-       switch (obj->parent->type)
+       if (ABC_HTML_OL == obj->parent->type && num_flag)
        {
        {
-               case ABC_HTML_OL:
-                       if (num_flag)
-                               cairo_move_to(cr, obj->parent->padding - (extents.x_bearing + extents.width + size / 2), -obj->y_bearing);
-                       else
-                               cairo_move_to(cr, extents.x_bearing + obj->parent->padding, -extents.y_bearing);
-                       break;
-               default:
-                       cairo_move_to(cr, extents.x_bearing + obj->parent->padding, -extents.y_bearing);
-                       break;
-       };
+               cairo_text_extents(cr, obj->text->data, &extents);
 
 
-       cairo_show_text(cr, obj->text->data);
-       cairo_stroke(cr);
+               cairo_move_to  (cr, obj->parent->padding - (extents.x_bearing + extents.width + size / 2), -obj->y_bearing);
+               cairo_show_text(cr, obj->text->data);
+               cairo_stroke(cr);
+               return 0;
+       }
+
+       double line_width = 0.5 + size / 16.0;
+       int    line_type  = ABC_LINE_NONE;
 
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_TEXT_DECORATION);
        if (attr) {
 
        attr = abc_obj_find_attr(obj, ABC_HTML_ATTR_TEXT_DECORATION);
        if (attr) {
-               double line = 0.5 + size / 16.0;
-               cairo_set_line_width(cr, line);
+               if (!__html_strcmp(attr->value->data, "underline"))
+                       line_type = ABC_LINE_UNDER;
 
 
-               if (!__html_strcmp(attr->value->data, "underline")) {
-                       cairo_move_to(cr, extents.x_bearing, -extents.y_bearing + line + 1.0);
-                       cairo_line_to(cr, extents.width,     -extents.y_bearing + line + 1.0);
+               else if (!__html_strcmp(attr->value->data, "overline"))
+                       line_type = ABC_LINE_OVER;
 
 
-               } else if (!__html_strcmp(attr->value->data, "overline")) {
-                       cairo_move_to(cr, extents.x_bearing, 1.0);
-                       cairo_line_to(cr, extents.width,     1.0);
+               else if (!__html_strcmp(attr->value->data, "line-through"))
+                       line_type = ABC_LINE_THROUGH;
+       }
+
+       if (!obj->text_splits) {
+               __draw_text(&extents, cr, obj->text->data, obj->parent->padding, 0.0, line_type, line_width);
+               return 0;
+       }
+
+       scf_string_t* s;
+       abc_text_t*   t;
+
+       double x = obj->parent->padding;
+       double y = 0.0;
 
 
-               } else if (!__html_strcmp(attr->value->data, "line-through")) {
-                       cairo_move_to(cr, extents.x_bearing, -extents.y_bearing / 2.0 + line);
-                       cairo_line_to(cr, extents.width,     -extents.y_bearing / 2.0 + line);
+       while  (obj->text_splits) {
+               t = obj->text_splits;
+
+               s = scf_string_cstr_len(obj->text->data + t->start, t->len);
+               if (!s) {
+                       scf_slist_clear(obj->text_splits, abc_text_t, next, free);
+                       return -ENOMEM;
                }
                }
-               cairo_stroke(cr);
+
+               __draw_text(&extents, cr, s->data, x, y, line_type, line_width);
+               y += extents.height;
+
+               obj->text_splits = t->next;
+
+               scf_string_free(s);
+               free(t);
        }
 
        return 0;
        }
 
        return 0;