123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142(*---------------------------------------------------------------------------
Copyright (c) 2026 The Raven authors. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)moduleColor=ColormoduleCmap=CmapmoduleTheme=Themetypet=Spec.ttypemarker=Spec.marker=Circle|Square|Triangle|Plus|Startypelegend_loc=Spec.legend_loc=|Upper_right|Upper_left|Lower_right|Lower_left|Center|Right|Upper_center|Lower_centertypeline_style=Spec.line_styletypescale=Spec.scaletypestretch=Spec.stretch(* Mark constructors *)letline=Spec.lineletpoint=Spec.pointletbar=Spec.barlethist=Spec.histletimage=Spec.imagelettext=Spec.textlethline=Spec.hlineletvline=Spec.vlineletabline=Spec.ablineletfill_between=Spec.fill_betweenlethspan=Spec.hspanletvspan=Spec.vspanleterrorbar=Spec.errorbarletheatmap=Spec.heatmapletimshow=Spec.imshowletcontour=Spec.contour(* Composition *)letlayers=Spec.layers(* Decorations *)lettitle=Spec.titleletxlabel=Spec.xlabelletylabel=Spec.ylabelletxlim=Spec.xlimletylim=Spec.ylimletxscale=Spec.xscaleletyscale=Spec.yscaleletxinvert=Spec.xinvertletyinvert=Spec.yinvertletgrid_lines=Spec.grid_linesletlegend=Spec.legendletxticks=Spec.xticksletyticks=Spec.yticksletwith_theme=Spec.with_themeletxtick_format=Spec.xtick_formatletytick_format=Spec.ytick_formatletframe=Spec.frameletno_axes=Spec.no_axes(* Layout *)letgrid=Spec.grid_layoutlethstack?gapspecs=Spec.grid_layout?gap[specs]letvstack?gapspecs=Spec.grid_layout?gap(List.map(funs->[s])specs)(* Rendering *)letdefault_width=1600.letdefault_height=1200.(* Use Cairo text measurement for all backends for consistent layout *)letresolve_with_cairo~theme~width~heightspec=letsurface=Ucairo.Image.create~w:1~h:1inletcr=Ucairo.createsurfaceinlettm=Cairo_backend.text_measurercrinletscene=Resolve.resolve~text_measurer:tm~theme~width~heightspecinUcairo.Surface.finishsurface;sceneletshow?(theme=Theme.default)?(width=default_width)?(height=default_height)spec=letprepared=Prepared.compile~themespecinCairo_backend.show_interactive~theme~width~heightpreparedletrender_png?(theme=Theme.default)?(width=default_width)?(height=default_height)filenamespec=letscene=resolve_with_cairo~theme~width~heightspecinCairo_backend.render_to_pngfilename~width~heightsceneletrender_pdf?(theme=Theme.default)?(width=default_width)?(height=default_height)filenamespec=letscene=resolve_with_cairo~theme~width~heightspecinCairo_backend.render_to_pdffilename~width~heightsceneletrender_svg?(theme=Theme.default)?(width=default_width)?(height=default_height)filenamespec=letscene=resolve_with_cairo~theme~width~heightspecinSvg_backend.render_to_filefilenamesceneletrender_svg_to_string?(theme=Theme.default)?(width=default_width)?(height=default_height)spec=letscene=resolve_with_cairo~theme~width~heightspecinSvg_backend.rendersceneletrender_to_buffer?(theme=Theme.default)?(width=default_width)?(height=default_height)spec=letscene=resolve_with_cairo~theme~width~heightspecinCairo_backend.render_to_buffer~width~heightsceneletinfer_dimensionsspec=letrecgrid_shape=function|Spec.Grid{rows;_}->letnrows=List.lengthrowsinletncols=List.fold_left(funaccrow->maxacc(List.lengthrow))0rowsinSome(nrows,ncols)|Spec.Decorated{inner;_}->grid_shapeinner|_->Noneinmatchgrid_shapespecwith|Some(nrows,ncols)whenncols>0->letcell_w=default_width/.floatncolsin(default_width,cell_w*.floatnrows)|_->(default_width,default_height)letppfmtspec=letwidth,height=infer_dimensionsspecinletbuf=render_to_buffer~width~heightspecinletb64=Image_util.base64_encodebufinFormat.fprintffmt""b64