FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_mermaid.c
Date: 2025-06-01 09:29:47
Exec Total Coverage
Lines: 0 288 0.0%
Functions: 0 11 0.0%
Branches: 0 174 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) The FFmpeg developers
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg 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 * FFmpeg 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 FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <limits.h>
22 #include <stdarg.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "avtextformat.h"
28 #include "tf_internal.h"
29 #include "tf_mermaid.h"
30 #include <libavutil/mem.h>
31 #include <libavutil/avassert.h>
32 #include <libavutil/bprint.h>
33 #include <libavutil/opt.h>
34
35
36 static const char *init_directive = ""
37 "%%{init: {"
38 "\"theme\": \"base\","
39 "\"curve\": \"monotoneX\","
40 "\"rankSpacing\": 10,"
41 "\"nodeSpacing\": 10,"
42 "\"themeCSS\": \"__###__\","
43 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
44 "\"themeVariables\": { "
45 "\"clusterBkg\": \"white\", "
46 "\"primaryBorderColor\": \"gray\", "
47 "\"lineColor\": \"gray\", "
48 "\"secondaryTextColor\": \"gray\", "
49 "\"tertiaryBorderColor\": \"gray\", "
50 "\"primaryTextColor\": \"#666\", "
51 "\"secondaryTextColor\": \"red\" "
52 "},"
53 "\"flowchart\": { "
54 "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
55 "\"diagramPadding\": 20, "
56 "\"curve\": \"monotoneX\" "
57 "}"
58 " }}%%\n\n";
59
60 static const char* init_directive_er = ""
61 "%%{init: {"
62 "\"theme\": \"base\","
63 "\"layout\": \"elk\","
64 "\"curve\": \"monotoneX\","
65 "\"rankSpacing\": 65,"
66 "\"nodeSpacing\": 60,"
67 "\"themeCSS\": \"__###__\","
68 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
69 "\"themeVariables\": { "
70 "\"clusterBkg\": \"white\", "
71 "\"primaryBorderColor\": \"gray\", "
72 "\"lineColor\": \"gray\", "
73 "\"secondaryTextColor\": \"gray\", "
74 "\"tertiaryBorderColor\": \"gray\", "
75 "\"primaryTextColor\": \"#666\", "
76 "\"secondaryTextColor\": \"red\" "
77 "},"
78 "\"er\": { "
79 "\"diagramPadding\": 12, "
80 "\"entityPadding\": 4, "
81 "\"minEntityWidth\": 150, "
82 "\"minEntityHeight\": 20, "
83 "\"curve\": \"monotoneX\" "
84 "}"
85 " }}%%\n\n";
86
87 static const char *theme_css_er = ""
88
89 // Variables
90 ".root { "
91 "--ff-colvideo: #6eaa7b; "
92 "--ff-colaudio: #477fb3; "
93 "--ff-colsubtitle: #ad76ab; "
94 "--ff-coltext: #666; "
95 "} "
96 " g.nodes g.node.default rect.basic.label-container, "
97 " g.nodes g.node.default path { "
98 " rx: 1; "
99 " ry: 1; "
100 " stroke-width: 1px !important; "
101 " stroke: #e9e9e9 !important; "
102 " fill: url(#ff-filtergradient) !important; "
103 " filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
104 " fill: white !important; "
105 " } "
106 " "
107 " .relationshipLine { "
108 " stroke: gray; "
109 " stroke-width: 1; "
110 " fill: none; "
111 " filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
112 " } "
113 " "
114 " g.node.default g.label.name foreignObject > div > span > p, "
115 " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
116 " font-size: 0.95rem; "
117 " font-weight: 500; "
118 " text-transform: uppercase; "
119 " min-width: 5.5rem; "
120 " margin-bottom: 0.5rem; "
121 " "
122 " } "
123 " "
124 " .edgePaths path { "
125 " marker-end: none; "
126 " marker-start: none; "
127 " "
128 "} ";
129
130
131 /* Mermaid Graph output */
132
133 typedef struct MermaidContext {
134 const AVClass *class;
135 AVDiagramConfig *diagram_config;
136 int subgraph_count;
137 int within_tag;
138 int indent_level;
139 int create_html;
140
141 // Options
142 int enable_link_colors; // Requires Mermaid 11.5
143
144 struct section_data {
145 const char *section_id;
146 const char *section_type;
147 const char *src_id;
148 const char *dest_id;
149 AVTextFormatLinkType link_type;
150 int current_is_textblock;
151 int current_is_stadium;
152 int subgraph_start_incomplete;
153 } section_data[SECTION_MAX_NB_LEVELS];
154
155 unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
156 AVBPrint link_buf; ///< print buffer for writing diagram links
157 AVDictionary *link_dict;
158 } MermaidContext;
159
160 #undef OFFSET
161 #define OFFSET(x) offsetof(MermaidContext, x)
162
163 static const AVOption mermaid_options[] = {
164 { "link_coloring", "enable colored links (requires Mermaid >= 11.5)", OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
165 ////{"diagram_css", "CSS for the diagram", OFFSET(diagram_css), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
166 ////{"html_template", "Template HTML", OFFSET(html_template), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
167 { NULL },
168 };
169
170 DEFINE_FORMATTER_CLASS(mermaid);
171
172 void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
173 {
174 MermaidContext *mmc = tfc->priv;
175 mmc->diagram_config = diagram_config;
176 }
177
178 static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
179 {
180 MermaidContext *mmc = tfc->priv;
181 AVBPrint buf;
182
183 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
184 av_bprintf(&buf, "%s--%s", src, dest);
185
186 if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
187 return 1;
188
189 av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
190
191 return 0;
192 }
193
194 static av_cold int mermaid_init(AVTextFormatContext *tfc)
195 {
196 MermaidContext *mmc = tfc->priv;
197
198 av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
199
200 ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
201 return 0;
202 }
203
204 static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
205 {
206 MermaidContext *mmc = tfc->priv;
207
208 int ret = mermaid_init(tfc);
209
210 if (ret < 0)
211 return ret;
212
213 mmc->create_html = 1;
214
215 return 0;
216 }
217
218 static av_cold int mermaid_uninit(AVTextFormatContext *tfc)
219 {
220 MermaidContext *mmc = tfc->priv;
221
222 av_bprint_finalize(&mmc->link_buf, NULL);
223 av_dict_free(&mmc->link_dict);
224
225 for (unsigned i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
226 av_freep(&mmc->section_data[i].dest_id);
227 av_freep(&mmc->section_data[i].section_id);
228 av_freep(&mmc->section_data[i].src_id);
229 av_freep(&mmc->section_data[i].section_type);
230 }
231
232 return 0;
233 }
234
235 static void set_str(const char **dst, const char *src)
236 {
237 if (*dst)
238 av_freep(dst);
239
240 if (src)
241 *dst = av_strdup(src);
242 }
243
244 #define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
245
246 static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
247 {
248 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
249 const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
250
251 if (!section)
252 return;
253 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
254 MermaidContext *mmc = tfc->priv;
255 const AVTextFormatSectionContext *sec_ctx = data;
256
257 if (tfc->level == 0) {
258 char *directive;
259 AVBPrint css_buf;
260 const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
261 char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
262 (void)theme_css_er;
263 ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
264 av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
265 av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
266 av_freep(&single_line_css);
267
268 directive = av_strireplace(diag_directive, "__###__", css_buf.str);
269 if (mmc->create_html) {
270 uint64_t length;
271 char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
272 if (!token_pos) {
273 av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
274 return;
275 }
276
277 length = token_pos - mmc->diagram_config->html_template;
278 for (uint64_t i = 0; i < length; i++)
279 writer_w8(tfc, mmc->diagram_config->html_template[i]);
280 }
281
282 writer_put_str(tfc, directive);
283 switch (mmc->diagram_config->diagram_type) {
284 case AV_DIAGRAMTYPE_GRAPH:
285 writer_put_str(tfc, "flowchart LR\n");
286 ////writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
287 writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%); \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%); \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%); \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
288 break;
289 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
290 writer_put_str(tfc, "erDiagram\n");
291 break;
292 }
293
294 av_bprint_finalize(&css_buf, NULL);
295 av_freep(&directive);
296 return;
297 }
298
299 if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
300
301 struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
302 AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
303
304 if (parent_sec_data.subgraph_start_incomplete) {
305
306 if (parent_buf->len > 0)
307 writer_printf(tfc, "%s", parent_buf->str);
308
309 writer_put_str(tfc, "</div>\"]\n");
310
311 mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
312 }
313 }
314
315 av_freep(&mmc->section_data[tfc->level].section_id);
316 av_freep(&mmc->section_data[tfc->level].section_type);
317 av_freep(&mmc->section_data[tfc->level].src_id);
318 av_freep(&mmc->section_data[tfc->level].dest_id);
319 mmc->section_data[tfc->level].current_is_textblock = 0;
320 mmc->section_data[tfc->level].current_is_stadium = 0;
321 mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
322 mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
323
324 // NOTE: av_strdup() allocations aren't checked
325 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
326
327 av_bprint_clear(buf);
328 writer_put_str(tfc, "\n");
329
330 mmc->indent_level++;
331
332 if (sec_ctx->context_id) {
333 MM_INDENT();
334 writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
335 } else {
336 av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
337 }
338
339 mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
340 set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
341 }
342
343 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
344
345 av_bprint_clear(buf);
346 writer_put_str(tfc, "\n");
347
348 mmc->indent_level++;
349
350 if (sec_ctx->context_id) {
351
352 set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
353
354 switch (mmc->diagram_config->diagram_type) {
355 case AV_DIAGRAMTYPE_GRAPH:
356 if (sec_ctx->context_flags & 1) {
357
358 MM_INDENT();
359 writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
360 mmc->section_data[tfc->level].current_is_textblock = 1;
361 } else if (sec_ctx->context_flags & 2) {
362
363 MM_INDENT();
364 writer_printf(tfc, "%s([\"", sec_ctx->context_id);
365 mmc->section_data[tfc->level].current_is_stadium = 1;
366 } else {
367 MM_INDENT();
368 writer_printf(tfc, "%s(\"", sec_ctx->context_id);
369 }
370
371 break;
372 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
373 MM_INDENT();
374 writer_printf(tfc, "%s {\n", sec_ctx->context_id);
375 break;
376 }
377
378 } else {
379 av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
380 }
381
382 set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
383 }
384
385
386 if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
387
388 if (sec_ctx && sec_ctx->context_type)
389 writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
390 else
391 writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
392 }
393
394
395 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
396
397 av_bprint_clear(buf);
398 mmc->nb_link_captions[tfc->level] = 0;
399
400 if (sec_ctx && sec_ctx->context_type)
401 set_str(&mmc->section_data[tfc->level].section_type, sec_ctx->context_type);
402
403 ////if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
404 //// AVBPrint buf;
405 //// av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
406 //// av_bprint_escape(&buf, section->get_type(data), NULL,
407 //// AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
408 //// writer_printf(tfc, " type=\"%s\"", buf.str);
409 }
410 }
411
412 static void mermaid_print_section_footer(AVTextFormatContext *tfc)
413 {
414 MermaidContext *mmc = tfc->priv;
415 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
416
417 if (!section)
418 return;
419 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
420 struct section_data sec_data = mmc->section_data[tfc->level];
421
422 if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
423 writer_put_str(tfc, "</div>");
424
425 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
426
427 switch (mmc->diagram_config->diagram_type) {
428 case AV_DIAGRAMTYPE_GRAPH:
429
430 if (sec_data.current_is_textblock) {
431 writer_printf(tfc, "\"}\n", section->name);
432
433 if (sec_data.section_id) {
434 MM_INDENT();
435 writer_put_str(tfc, "class ");
436 writer_put_str(tfc, sec_data.section_id);
437 writer_put_str(tfc, " ff-");
438 writer_put_str(tfc, section->name);
439 writer_put_str(tfc, "\n");
440 }
441 } else if (sec_data.current_is_stadium) {
442 writer_printf(tfc, "\"]):::ff-%s\n", section->name);
443 } else {
444 writer_printf(tfc, "\"):::ff-%s\n", section->name);
445 }
446
447 break;
448 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
449 MM_INDENT();
450 writer_put_str(tfc, "}\n\n");
451 break;
452 }
453
454 mmc->indent_level--;
455
456 } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
457
458 MM_INDENT();
459 writer_put_str(tfc, "end\n");
460
461 if (sec_data.section_id) {
462 MM_INDENT();
463 writer_put_str(tfc, "class ");
464 writer_put_str(tfc, sec_data.section_id);
465 writer_put_str(tfc, " ff-");
466 writer_put_str(tfc, section->name);
467 writer_put_str(tfc, "\n");
468 }
469
470 mmc->indent_level--;
471 }
472
473 if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
474 if (sec_data.src_id && sec_data.dest_id
475 && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
476 switch (mmc->diagram_config->diagram_type) {
477 case AV_DIAGRAMTYPE_GRAPH:
478
479 if (sec_data.section_type && mmc->enable_link_colors)
480 av_bprintf(&mmc->link_buf, "\n %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
481 else
482 av_bprintf(&mmc->link_buf, "\n %s ==", sec_data.src_id);
483
484 if (buf->len > 0) {
485 av_bprintf(&mmc->link_buf, " \"%s", buf->str);
486
487 for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
488 av_bprintf(&mmc->link_buf, "<br>&nbsp;");
489
490 av_bprintf(&mmc->link_buf, "\" ==");
491 }
492
493 av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
494
495 break;
496 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
497
498
499 av_bprintf(&mmc->link_buf, "\n %s", sec_data.src_id);
500
501 switch (sec_data.link_type) {
502 case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
503 av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
504 break;
505 case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
506 av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
507 break;
508 case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
509 av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
510 break;
511 case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
512 av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
513 break;
514 default:
515 av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
516 break;
517 }
518
519 av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
520
521 break;
522 }
523
524 if (tfc->level == 0) {
525
526 writer_put_str(tfc, "\n");
527 if (mmc->create_html) {
528 char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
529 if (!token_pos) {
530 av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
531 return;
532 }
533 token_pos += strlen("__###__");
534 writer_put_str(tfc, token_pos);
535 }
536 }
537
538 if (tfc->level == 1) {
539
540 if (mmc->link_buf.len > 0) {
541 writer_put_str(tfc, mmc->link_buf.str);
542 av_bprint_clear(&mmc->link_buf);
543 }
544
545 writer_put_str(tfc, "\n");
546 }
547 }
548
549 static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
550 const char *str, int64_t num, const int is_int)
551 {
552 MermaidContext *mmc = tfc->priv;
553 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
554
555 if (!section)
556 return;
557
558 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
559 struct section_data sec_data = mmc->section_data[tfc->level];
560 int exit = 0;
561
562 if (section->id_key && !strcmp(section->id_key, key)) {
563 set_str(&mmc->section_data[tfc->level].section_id, str);
564 exit = 1;
565 }
566
567 if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
568 set_str(&mmc->section_data[tfc->level].dest_id, str);
569 exit = 1;
570 }
571
572 if (section->src_id_key && !strcmp(section->src_id_key, key)) {
573 set_str(&mmc->section_data[tfc->level].src_id, str);
574 exit = 1;
575 }
576
577 if (section->linktype_key && !strcmp(section->linktype_key, key)) {
578 mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
579 exit = 1;
580 }
581
582 //if (exit)
583 // return;
584
585 if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
586 || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
587
588 if (exit)
589 return;
590
591 switch (mmc->diagram_config->diagram_type) {
592 case AV_DIAGRAMTYPE_GRAPH:
593
594 if (is_int) {
595 writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
596 } else {
597 ////AVBPrint b;
598 ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
599 const char *tmp = av_strireplace(str, "\"", "'");
600 ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
601 writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
602 av_freep(&tmp);
603 }
604
605 break;
606 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
607
608 if (!is_int && str)
609 {
610 const char *col_type;
611
612 if (key[0] == '_')
613 return;
614
615 if (sec_data.section_id && !strcmp(str, sec_data.section_id))
616 col_type = "PK";
617 else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
618 col_type = "FK";
619 else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
620 col_type = "FK";
621 else
622 col_type = "";
623
624 MM_INDENT();
625
626 if (is_int)
627 writer_printf(tfc, " %s %"PRId64" %s\n", key, num, col_type);
628 else
629 writer_printf(tfc, " %s %s %s\n", key, str, col_type);
630 }
631 break;
632 }
633
634 } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
635
636 if (exit)
637 return;
638
639 if (buf->len > 0)
640 av_bprintf(buf, "%s", "<br>");
641
642 av_bprintf(buf, "");
643 if (is_int)
644 av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
645 else
646 av_bprintf(buf, "<span>%s</span>", str);
647
648 mmc->nb_link_captions[tfc->level]++;
649 }
650 }
651
652 static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
653 {
654 mermaid_print_value(tfc, key, value, 0, 0);
655 }
656
657 static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
658 {
659 mermaid_print_value(tfc, key, NULL, value, 1);
660 }
661
662 const AVTextFormatter avtextformatter_mermaid = {
663 .name = "mermaid",
664 .priv_size = sizeof(MermaidContext),
665 .init = mermaid_init,
666 .uninit = mermaid_uninit,
667 .print_section_header = mermaid_print_section_header,
668 .print_section_footer = mermaid_print_section_footer,
669 .print_integer = mermaid_print_int,
670 .print_string = mermaid_print_str,
671 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
672 .priv_class = &mermaid_class,
673 };
674
675
676 const AVTextFormatter avtextformatter_mermaidhtml = {
677 .name = "mermaidhtml",
678 .priv_size = sizeof(MermaidContext),
679 .init = mermaid_init_html,
680 .uninit = mermaid_uninit,
681 .print_section_header = mermaid_print_section_header,
682 .print_section_footer = mermaid_print_section_footer,
683 .print_integer = mermaid_print_int,
684 .print_string = mermaid_print_str,
685 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
686 .priv_class = &mermaid_class,
687 };
688