{"record":{"id":"a01ae49eef3e76fb","repo":"d2lang/d2","slug":"text-not-pre-measured-and-no-ruler-provided","errorCode":null,"errorMessage":"text not pre-measured and no ruler provided","messagePattern":"text not pre-measured and no ruler provided","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2graph/d2graph.go","lineNumber":1453,"sourceCode":"\nfunc getMarkdownDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText, fontFamily *d2fonts.FontFamily, monoFontFamily *d2fonts.FontFamily) (*d2target.TextDimensions, error) {\n\tif dims := findMeasured(mtexts, t); dims != nil {\n\t\treturn dims, nil\n\t}\n\n\tif ruler != nil {\n\t\twidth, height, err := textmeasure.MeasureMarkdown(t.Text, ruler, fontFamily, monoFontFamily, t.FontSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn d2target.NewTextDimensions(width, height), nil\n\t}\n\n\tif strings.TrimSpace(t.Text) == \"\" {\n\t\treturn d2target.NewTextDimensions(1, 1), nil\n\t}\n\n\treturn nil, fmt.Errorf(\"text not pre-measured and no ruler provided\")\n}\n\nfunc GetTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText, fontFamily *d2fonts.FontFamily) *d2target.TextDimensions {\n\treturn GetTextDimensionsWithMono(mtexts, ruler, t, fontFamily, nil)\n}\n\nfunc GetTextDimensionsWithMono(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText, fontFamily *d2fonts.FontFamily, monoFontFamily *d2fonts.FontFamily) *d2target.TextDimensions {\n\tif dims := findMeasured(mtexts, t); dims != nil {\n\t\treturn dims\n\t}\n\n\tif ruler != nil {\n\t\tvar w int\n\t\tvar h int\n\t\tif t.Language != \"\" {\n\t\t\toriginalLineHeight := ruler.LineHeightFactor\n\t\t\truler.LineHeightFactor = textmeasure.CODE_LINE_HEIGHT\n\t\t\tif monoFontFamily != nil {","sourceCodeStart":1435,"sourceCodeEnd":1471,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2graph/d2graph.go#L1435-L1471","documentation":"GetTextDimensionsWithMono reached its terminal fallback: the text was not found in the pre-measured mtexts list, the ruler is nil (or couldn't measure), and the trimmed text is non-empty. It returns this sentinel error meaning measurement was impossible — callers like the label/edge sizing paths surface it or treat nil as 'not found'.","triggerScenarios":"Calling GetTextDimensions/GetTextDimensionsWithMono with a nil ruler for a non-empty text that has no matching entry in mtexts (same text, fontSize, font family, and bold/italic flags).","commonSituations":"Embedding d2graph without textmeasure (e.g. no fonts bundled); passing mtexts generated before text/font changes; mismatched MText attributes causing lookup misses; custom render pipelines skipping graph.SetText.","solutions":["Create and pass a textmeasure.Ruler (textmeasure.NewRuler() with fonts initialized) so text is measured on demand","Ensure mtexts contains an exact MText match — same Text, FontSize, fontFamily, bold/italic — for every measured string","Regenerate mtexts via graph.SetText after any text or style change","If the text can legitimately be empty, it's already handled: empty/whitespace text returns 1x1 dimensions without error"],"exampleFix":"// before\ndims := d2graph.GetTextDimensions(mtexts, nil, mt, fontFamily)\n// after\nruler, _ := textmeasure.NewRuler()\ndims := d2graph.GetTextDimensions(mtexts, ruler, mt, fontFamily)","handlingStrategy":"validation","validationCode":"// guard before calling GetTextDimensions without a ruler\nfunc measure(mtexts []*d2target.MText, t *d2target.MText, ff *d2fonts.FontFamily) (*d2target.TextDimensions, error) {\n    if strings.TrimSpace(t.Text) == \"\" {\n        return d2target.NewTextDimensions(1, 1), nil\n    }\n    if ruler == nil {\n        for _, mt := range mtexts {\n            if mt.Text == t.Text && mt.FontSize == t.FontSize {\n                // only safe if an exact pre-measured entry exists\n                return d2target.NewTextDimensions(mt.Width, mt.Height), nil\n            }\n        }\n        return nil, fmt.Errorf(\"text %q not pre-measured; ruler required\", t.Text)\n    }\n    return d2graph.GetTextDimensions(mtexts, ruler, t, ff), nil\n}","typeGuard":"func isPreMeasured(mtexts []*d2target.MText, t *d2target.MText) bool {\n    for _, mt := range mtexts {\n        if mt.Text == t.Text && mt.FontSize == t.FontSize &&\n            mt.FontFamily == t.FontFamily && mt.Bold == t.Bold && mt.Italic == t.Italic {\n            return true\n        }\n    }\n    return false\n}","tryCatchPattern":"dims := d2graph.GetTextDimensions(mtexts, ruler, mt, fontFamily)\nif dims == nil {\n    return fmt.Errorf(\"text %q not pre-measured and no ruler provided\", mt.Text)\n}","preventionTips":["Always initialize a textmeasure.Ruler and pass it; nil rulers only work if every string is pre-measured","Match MText fields exactly (text, size, family, bold/italic) when pre-measuring","Call graph.SetText after graph construction so mtexts is complete","Never assume whitespace-only text needs measurement — it short-circuits to 1x1"],"tags":["go","d2","text-measurement"],"backgroundTag":"text-dimensions-not-found","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}