FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_mermaid.c
Date: 2026-09-27 22:20:00
Exec Total Coverage
Lines: 0 288 0.0%
Functions: 0 12 0.0%
Branches: 0 170 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 <inttypes.h>
22 #include <limits.h>
23 #include <stdarg.h>
24 #include <string.h>
25
26 #include "avtextformat.h"
27 #include "tf_internal.h"
28 #include "tf_mermaid.h"
29 #include "libavutil/bprint.h"
30 #include "libavutil/mem.h"
31 #include "libavutil/opt.h"
32
33
34 static const char *init_directive = ""
35 "%%{init: {"
36 "\"theme\": \"base\","
37 "\"curve\": \"monotoneX\","
38 "\"rankSpacing\": 10,"
39 "\"nodeSpacing\": 10,"
40 "\"themeCSS\": \"__###__\","
41 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
42 "\"themeVariables\": { "
43 "\"clusterBkg\": \"white\", "
44 "\"primaryBorderColor\": \"gray\", "
45 "\"lineColor\": \"gray\", "
46 "\"secondaryTextColor\": \"gray\", "
47 "\"tertiaryBorderColor\": \"gray\", "
48 "\"primaryTextColor\": \"#666\", "
49 "\"secondaryTextColor\": \"red\" "
50 "},"
51 "\"flowchart\": { "
52 "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
53 "\"diagramPadding\": 20, "
54 "\"curve\": \"monotoneX\" "
55 "}"
56 " }}%%\n\n";
57
58 static const char* init_directive_er = ""
59 "%%{init: {"
60 "\"theme\": \"base\","
61 "\"layout\": \"elk\","
62 "\"curve\": \"monotoneX\","
63 "\"rankSpacing\": 65,"
64 "\"nodeSpacing\": 60,"
65 "\"themeCSS\": \"__###__\","
66 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
67 "\"themeVariables\": { "
68 "\"clusterBkg\": \"white\", "
69 "\"primaryBorderColor\": \"gray\", "
70 "\"lineColor\": \"gray\", "
71 "\"secondaryTextColor\": \"gray\", "
72 "\"tertiaryBorderColor\": \"gray\", "
73 "\"primaryTextColor\": \"#666\", "
74 "\"secondaryTextColor\": \"red\" "
75 "},"
76 "\"er\": { "
77 "\"diagramPadding\": 12, "
78 "\"entityPadding\": 4, "
79 "\"minEntityWidth\": 150, "
80 "\"minEntityHeight\": 20, "
81 "\"curve\": \"monotoneX\" "
82 "}"
83 " }}%%\n\n";
84
85 static const char *theme_css_er = ""
86
87 // Variables
88 ".root { "
89 "--ff-colvideo: #6eaa7b; "
90 "--ff-colaudio: #477fb3; "
91 "--ff-colsubtitle: #ad76ab; "
92 "--ff-coltext: #666; "
93 "} "
94 " g.nodes g.node.default rect.basic.label-container, "
95 " g.nodes g.node.default path { "
96 " rx: 1; "
97 " ry: 1; "
98 " stroke-width: 1px !important; "
99 " stroke: #e9e9e9 !important; "
100 " fill: url(#ff-filtergradient) !important; "
101 " filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
102 " fill: white !important; "
103 " } "
104 " "
105 " .relationshipLine { "
106 " stroke: gray; "
107 " stroke-width: 1; "
108 " fill: none; "
109 " filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
110 " } "
111 " "
112 " g.node.default g.label.name foreignObject > div > span > p, "
113 " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
114 " font-size: 0.95rem; "
115 " font-weight: 500; "
116 " text-transform: uppercase; "
117 " min-width: 5.5rem; "
118 " margin-bottom: 0.5rem; "
119 " "
120 " } "
121 " "
122 " .edgePaths path { "
123 " marker-end: none; "
124 " marker-start: none; "
125 " "
126 "} ";
127
128
129 /* Mermaid Graph output */
130
131 typedef struct MermaidContext {
132 const AVClass *class;
133 AVDiagramConfig *diagram_config;
134 int subgraph_count;
135 int within_tag;
136 int indent_level;
137 int create_html;
138
139 // Options
140 int enable_link_colors; // Requires Mermaid 11.5
141
142 struct section_data {
143 const char *section_id;
144 const char *section_type;
145 const char *src_id;
146 const char *dest_id;
147 AVTextFormatLinkType link_type;
148 int current_is_textblock;
149 int current_is_stadium;
150 int subgraph_start_incomplete;
151 } section_data[SECTION_MAX_NB_LEVELS];
152
153 unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
154 AVBPrint link_buf; ///< print buffer for writing diagram links
155 AVDictionary *link_dict;
156 } MermaidContext;
157
158 #undef OFFSET
159 #define OFFSET(x) offsetof(MermaidContext, x)
160
161 static const AVOption mermaid_options[] = {
162 { "link_coloring", "enable colored links (requires Mermaid >= 11.5)", OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
163 ////{"diagram_css", "CSS for the diagram", OFFSET(diagram_css), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
164 ////{"html_template", "Template HTML", OFFSET(html_template), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
165 { NULL },
166 };
167
168 DEFINE_FORMATTER_CLASS(mermaid);
169
170 ✗ void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
171 {
172 ✗ MermaidContext *mmc = tfc->priv;
173 ✗ mmc->diagram_config = diagram_config;
174 ✗ }
175
176 ✗ static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
177 {
178 ✗ MermaidContext *mmc = tfc->priv;
179 AVBPrint buf;
180
181 ✗ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
182 ✗ av_bprintf(&buf, "%s--%s", src, dest);
183
184 ✗ if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
185 ✗ return 1;
186
187 ✗ av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
188
189 ✗ return 0;
190 }
191
192 ✗ static av_cold int mermaid_init(AVTextFormatContext *tfc)
193 {
194 ✗ MermaidContext *mmc = tfc->priv;
195
196 ✗ av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
197
198 ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
199 ✗ return 0;
200 }
201
202 ✗ static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
203 {
204 ✗ MermaidContext *mmc = tfc->priv;
205
206 ✗ int ret = mermaid_init(tfc);
207
208 ✗ if (ret < 0)
209 ✗ return ret;
210
211 ✗ mmc->create_html = 1;
212
213 ✗ return 0;
214 }
215
216 ✗ static av_cold int mermaid_uninit(AVTextFormatContext *tfc)
217 {
218 ✗ MermaidContext *mmc = tfc->priv;
219
220 ✗ av_bprint_finalize(&mmc->link_buf, NULL);
221 ✗ av_dict_free(&mmc->link_dict);
222
223 ✗ for (unsigned i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
224 ✗ av_freep(&mmc->section_data[i].dest_id);
225 ✗ av_freep(&mmc->section_data[i].section_id);
226 ✗ av_freep(&mmc->section_data[i].src_id);
227 ✗ av_freep(&mmc->section_data[i].section_type);
228 }
229
230 ✗ return 0;
231 }
232
233 ✗ static void set_str(const char **dst, const char *src)
234 {
235 ✗ if (*dst)
236 ✗ av_freep(dst);
237
238 ✗ if (src)
239 ✗ *dst = av_strdup(src);
240 ✗ }
241
242 ✗ static void mermaid_subgraph_complete_start(MermaidContext *mmc, AVTextFormatContext *tfc, int level) {
243 ✗ struct section_data parent_sec_data = mmc->section_data[level];
244 ✗ AVBPrint *parent_buf = &tfc->section_pbuf[level];
245
246 ✗ if (parent_sec_data.subgraph_start_incomplete) {
247 ✗ if (parent_buf->len > 0)
248 ✗ writer_printf(tfc, "%s", parent_buf->str);
249
250 ✗ writer_put_str(tfc, "</div>\"]\n");
251
252 ✗ mmc->section_data[level].subgraph_start_incomplete = 0;
253 }
254 ✗ }
255
256 #define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
257
258 ✗ static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
259 {
260 ✗ const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
261 ✗ const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
262
263 ✗ if (!section)
264 ✗ return;
265 ✗ AVBPrint *buf = &tfc->section_pbuf[tfc->level];
266 ✗ MermaidContext *mmc = tfc->priv;
267 ✗ const AVTextFormatSectionContext *sec_ctx = data;
268
269 ✗ if (tfc->level == 0) {
270 char *directive;
271 AVBPrint css_buf;
272 ✗ const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
273 ✗ char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
274 (void)theme_css_er;
275 ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
276 ✗ av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
277 ✗ av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
278 ✗ av_freep(&single_line_css);
279
280 ✗ directive = av_strireplace(diag_directive, "__###__", css_buf.str);
281 ✗ if (mmc->create_html) {
282 uint64_t length;
283 ✗ char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
284 ✗ if (!token_pos) {
285 ✗ av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
286 ✗ return;
287 }
288
289 ✗ length = token_pos - mmc->diagram_config->html_template;
290 ✗ for (uint64_t i = 0; i < length; i++)
291 ✗ writer_w8(tfc, mmc->diagram_config->html_template[i]);
292 }
293
294 ✗ writer_put_str(tfc, directive);
295 ✗ switch (mmc->diagram_config->diagram_type) {
296 ✗ case AV_DIAGRAMTYPE_GRAPH:
297 ✗ writer_put_str(tfc, "flowchart LR\n");
298 ////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");
299 ✗ 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");
300 ✗ break;
301 ✗ case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
302 ✗ writer_put_str(tfc, "erDiagram\n");
303 ✗ break;
304 }
305
306 ✗ av_bprint_finalize(&css_buf, NULL);
307 ✗ av_freep(&directive);
308 ✗ return;
309 }
310
311 ✗ if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
312 ✗ mermaid_subgraph_complete_start(mmc, tfc, tfc->level - 1);
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 ✗ mermaid_subgraph_complete_start(mmc, tfc, tfc->level);
459
460 ✗ MM_INDENT();
461 ✗ writer_put_str(tfc, "end\n");
462
463 ✗ if (sec_data.section_id) {
464 ✗ MM_INDENT();
465 ✗ writer_put_str(tfc, "class ");
466 ✗ writer_put_str(tfc, sec_data.section_id);
467 ✗ writer_put_str(tfc, " ff-");
468 ✗ writer_put_str(tfc, section->name);
469 ✗ writer_put_str(tfc, "\n");
470 }
471
472 ✗ mmc->indent_level--;
473 }
474
475 ✗ if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
476 ✗ if (sec_data.src_id && sec_data.dest_id
477 ✗ && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
478 ✗ switch (mmc->diagram_config->diagram_type) {
479 ✗ case AV_DIAGRAMTYPE_GRAPH:
480
481 ✗ if (sec_data.section_type && mmc->enable_link_colors)
482 ✗ 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);
483 else
484 ✗ av_bprintf(&mmc->link_buf, "\n %s ==", sec_data.src_id);
485
486 ✗ if (buf->len > 0) {
487 ✗ av_bprintf(&mmc->link_buf, " \"%s", buf->str);
488
489 ✗ for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
490 ✗ av_bprintf(&mmc->link_buf, "<br>&nbsp;");
491
492 ✗ av_bprintf(&mmc->link_buf, "\" ==");
493 }
494
495 ✗ av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
496
497 ✗ break;
498 ✗ case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
499
500
501 ✗ av_bprintf(&mmc->link_buf, "\n %s", sec_data.src_id);
502
503 ✗ switch (sec_data.link_type) {
504 ✗ case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
505 ✗ av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
506 ✗ break;
507 ✗ case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
508 ✗ av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
509 ✗ break;
510 ✗ case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
511 ✗ av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
512 ✗ break;
513 ✗ case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
514 ✗ av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
515 ✗ break;
516 ✗ default:
517 ✗ av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
518 ✗ break;
519 }
520
521 ✗ av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
522
523 ✗ break;
524 }
525
526 ✗ if (tfc->level == 0) {
527
528 ✗ writer_put_str(tfc, "\n");
529 ✗ if (mmc->create_html) {
530 ✗ char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
531 ✗ if (!token_pos) {
532 ✗ av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
533 ✗ return;
534 }
535 ✗ token_pos += strlen("__###__");
536 ✗ writer_put_str(tfc, token_pos);
537 }
538 }
539
540 ✗ if (tfc->level == 1) {
541
542 ✗ if (mmc->link_buf.len > 0) {
543 ✗ writer_put_str(tfc, mmc->link_buf.str);
544 ✗ av_bprint_clear(&mmc->link_buf);
545 }
546
547 ✗ writer_put_str(tfc, "\n");
548 }
549 }
550
551 ✗ static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
552 const char *str, int64_t num, const int is_int)
553 {
554 ✗ MermaidContext *mmc = tfc->priv;
555 ✗ const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
556
557 ✗ if (!section)
558 ✗ return;
559
560 ✗ AVBPrint *buf = &tfc->section_pbuf[tfc->level];
561 ✗ struct section_data sec_data = mmc->section_data[tfc->level];
562 ✗ int exit = 0;
563
564 ✗ if (section->id_key && !strcmp(section->id_key, key)) {
565 ✗ set_str(&mmc->section_data[tfc->level].section_id, str);
566 ✗ exit = 1;
567 }
568
569 ✗ if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
570 ✗ set_str(&mmc->section_data[tfc->level].dest_id, str);
571 ✗ exit = 1;
572 }
573
574 ✗ if (section->src_id_key && !strcmp(section->src_id_key, key)) {
575 ✗ set_str(&mmc->section_data[tfc->level].src_id, str);
576 ✗ exit = 1;
577 }
578
579 ✗ if (section->linktype_key && !strcmp(section->linktype_key, key)) {
580 ✗ mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;
581 ✗ exit = 1;
582 }
583
584 ✗ if (exit)
585 ✗ return;
586
587 ✗ if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
588 ✗ || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
589 ✗ switch (mmc->diagram_config->diagram_type) {
590 ✗ case AV_DIAGRAMTYPE_GRAPH:
591
592 ✗ if (is_int) {
593 ✗ writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
594 } else {
595 ✗ const char *tmp = av_strireplace(str, "\"", "'");
596 ✗ writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
597 ✗ av_freep(&tmp);
598 }
599
600 ✗ break;
601 ✗ case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
602
603 ✗ if (!is_int && str)
604 {
605 const char *col_type;
606
607 ✗ if (key[0] == '_')
608 ✗ return;
609
610 ✗ if (sec_data.section_id && !strcmp(str, sec_data.section_id))
611 ✗ col_type = "PK";
612 ✗ else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
613 ✗ col_type = "FK";
614 ✗ else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
615 ✗ col_type = "FK";
616 else
617 ✗ col_type = "";
618
619 ✗ MM_INDENT();
620
621 ✗ writer_printf(tfc, " %s %s %s\n", key, str, col_type);
622 }
623 ✗ break;
624 }
625
626 ✗ } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
627 ✗ if (buf->len > 0)
628 ✗ av_bprintf(buf, "%s", "<br>");
629
630 ✗ av_bprintf(buf, "");
631 ✗ if (is_int)
632 ✗ av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
633 else
634 ✗ av_bprintf(buf, "<span>%s</span>", str);
635
636 ✗ mmc->nb_link_captions[tfc->level]++;
637 }
638 }
639
640 ✗ static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
641 {
642 ✗ mermaid_print_value(tfc, key, value, 0, 0);
643 ✗ }
644
645 ✗ static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
646 {
647 ✗ mermaid_print_value(tfc, key, NULL, value, 1);
648 ✗ }
649
650 const AVTextFormatter avtextformatter_mermaid = {
651 .name = "mermaid",
652 .priv_size = sizeof(MermaidContext),
653 .init = mermaid_init,
654 .uninit = mermaid_uninit,
655 .print_section_header = mermaid_print_section_header,
656 .print_section_footer = mermaid_print_section_footer,
657 .print_integer = mermaid_print_int,
658 .print_string = mermaid_print_str,
659 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
660 .priv_class = &mermaid_class,
661 };
662
663
664 const AVTextFormatter avtextformatter_mermaidhtml = {
665 .name = "mermaidhtml",
666 .priv_size = sizeof(MermaidContext),
667 .init = mermaid_init_html,
668 .uninit = mermaid_uninit,
669 .print_section_header = mermaid_print_section_header,
670 .print_section_footer = mermaid_print_section_footer,
671 .print_integer = mermaid_print_int,
672 .print_string = mermaid_print_str,
673 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
674 .priv_class = &mermaid_class,
675 };
676