Repository: mozman/ezdxf

Issue:
Matplotlib font properties
Hi again!

In dxf-to-png conversion, I ran into a text rendering issues for DXF files containing CJK (Chinese, Japanese, Korean) 
characters. 

For instance, I have a `simple_text.dxf` containing Korean characters, which is visualized with `cad_viewer.py` with no problem:
<img width="1595" alt="simpletest_cadviewer" src="https://user-images.githubusercontent.com/24424825/94133541-bfd4c600-fe9b-11ea-9ad3-ec85eb13f2c4.png">


When the DXF file is rasterized with dxf2png tool, the output image displays Korean characters as squares:
<img width="1387" alt="initial" src="https://user-images.githubusercontent.com/24424825/94133722-ff031700-fe9b-11ea-8d34-e414a300eb84.png">


When I added a new Korean font (i.e., `NanumBarunGothic.otf`) by specifying its filename to `FontProperties` and passed it to the backend params, the characters are displayed properly.
```
... (omitted for brevity)
from matplotlib.font_manager import FontProperties

myfont = FontProperties(fname='/Library/Fonts/NanumBarunGothic.otf', size=15)

doc = ezdxf.readfile('./simple_text.dxf')
msp = doc.modelspace()
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
ctx.set_current_layout(msp)
out = MatplotlibBackend(ax, font=myfont)
Frontend(ctx, out).draw_layout(msp, finalize=True)
fig.savefig('simple_text.png', dpi=300, transparent=True)
plt.close(fig)
```

<img width="1394" alt="fontadded" src="https://user-images.githubusercontent.com/24424825/94133939-4c7f8400-fe9c-11ea-91ee-6c069f7f197f.png">


However, there were some cases in which the following error message was returned:
```
    Frontend(ctx, out).draw_layout(msp, finalize=True)
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/frontend.py", line 93, in draw_layout
    self.draw_entities(reorder.ascending(layout, handle_mapping))
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/frontend.py", line 113, in draw_entities
    self.draw_entity(entity, properties)
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/frontend.py", line 133, in draw_entity
    self.draw_text_entity_2d(entity, properties)
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/frontend.py", line 180, in draw_text_entity_2d
    for line, transform, cap_height in simplified_text_chunks(
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/text.py", line 279, in simplified_text_chunks
    font_measurements = out.get_font_measurements(cap_height, font=font)
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/matplotlib.py", line 186, in get_font_measurements
    return self._text.font_measurements.scale_from_baseline(
  File "/usr/local/lib/python3.8/dist-packages/ezdxf/addons/drawing/text.py", line 75, in scale_from_baseline
    assert math.isclose(self.baseline, 0.0)
```


I'm not sure if this error stems from the font measurements of a particular font or something else. From the error message, I might infer that there is a problem with the baseline of the font not being 0? I appreciate your explanations.

Discussion:

Patch:
diff --git a/src/ezdxf/addons/drawing/matplotlib.py b/src/ezdxf/addons/drawing/matplotlib.py
index 9337dc888..881e8bac5 100644
--- a/src/ezdxf/addons/drawing/matplotlib.py
+++ b/src/ezdxf/addons/drawing/matplotlib.py
@@ -244,11 +244,12 @@ class TextRenderer:
         upper_x = self.get_text_path('X').vertices[:, 1].tolist()
         lower_x = self.get_text_path('x').vertices[:, 1].tolist()
         lower_p = self.get_text_path('p').vertices[:, 1].tolist()
+        baseline = min(lower_x)
         return FontMeasurements(
-            baseline=min(lower_x),
-            cap_top=max(upper_x),
-            x_top=max(lower_x),
-            bottom=min(lower_p)
+            baseline=baseline,
+            cap_height=max(upper_x) - baseline,
+            x_height=max(lower_x) - baseline,
+            descender_height=baseline - min(lower_p)
         )
 
     def get_text_path(self, text: str) -> TextPath:
diff --git a/src/ezdxf/addons/drawing/pyqt.py b/src/ezdxf/addons/drawing/pyqt.py
index 6c00fedcd..42fc3eed3 100644
--- a/src/ezdxf/addons/drawing/pyqt.py
+++ b/src/ezdxf/addons/drawing/pyqt.py
@@ -250,11 +250,12 @@ class TextRenderer:
         upper_x = self.get_text_rect('X')
         lower_x = self.get_text_rect('x')
         lower_p = self.get_text_rect('p')
+        baseline = lower_x.bottom()
         return FontMeasurements(
-            baseline=lower_x.bottom(),
-            cap_top=upper_x.top(),
-            x_top=lower_x.top(),
-            bottom=lower_p.bottom(),
+            baseline=baseline,
+            cap_height=upper_x.top() - baseline,
+            x_height=lower_x.top() - baseline,
+            descender_height=baseline - lower_p.bottom(),
         )
 
     def get_text_path(self, text: str) -> qg.QPainterPath:
diff --git a/src/ezdxf/addons/drawing/text.py b/src/ezdxf/addons/drawing/text.py
index e42823d89..e4127069f 100644
--- a/src/ezdxf/addons/drawing/text.py
+++ b/src/ezdxf/addons/drawing/text.py
@@ -57,40 +57,39 @@ assert len(DXF_MTEXT_ALIGNMENT_TO_ALIGNMENT) == len(DXFConstants.MTEXT_ALIGN_FLA
 
 
 class FontMeasurements:
-    def __init__(self, baseline: float, cap_top: float, x_top: float, bottom: float):
+    def __init__(self, baseline: float, cap_height: float, x_height: float, descender_height: float):
         self.baseline = baseline
-        self.cap_top = cap_top
-        self.x_top = x_top
-        self.bottom = bottom
+        self.cap_height = cap_height
+        self.x_height = x_height
+        self.descender_height = descender_height
 
     def __eq__(self, other):
         return (isinstance(other, FontMeasurements) and
                 self.baseline == other.baseline and
-                self.cap_top == other.cap_top and
-                self.x_top == other.x_top and
-                self.bottom == other.bottom)
+                self.cap_height == other.cap_height and
+                self.x_height == other.x_height and
+                self.descender_height == other.descender_height)
 
     def scale_from_baseline(self, desired_cap_height: float) -> "FontMeasurements":
         scale = desired_cap_height / self.cap_height
-        assert math.isclose(self.baseline, 0.0)
         return FontMeasurements(
             baseline=self.baseline,
-            cap_top=desired_cap_height,
-            x_top=self.x_height * scale,
-            bottom=self.bottom * scale,
+            cap_height=desired_cap_height,
+            x_height=self.x_height * scale,
+            descender_height=self.descender_height * scale,
         )
 
     @property
-    def cap_height(self) -> float:
-        return abs(self.cap_top - self.baseline)
+    def cap_top(self) -> float:
+        return self.baseline + self.cap_height
 
     @property
-    def x_height(self) -> float:
-        return abs(self.x_top - self.baseline)
+    def x_top(self) -> float:
+        return self.baseline + self.x_height
 
     @property
-    def descender_height(self) -> float:
-        return abs(self.baseline - self.bottom)
+    def bottom(self) -> float:
+        return self.baseline - self.descender_height
 
 
 def _get_rotation(text: AnyText) -> Matrix44:
@@ -211,7 +210,6 @@ def _get_extra_transform(text: AnyText) -> Matrix44:
 
 def _apply_alignment(alignment: Alignment,
                      line_widths: List[float],
-                     cap_height: float,
                      line_spacing: float,
                      box_width: Optional[float],
                      font_measurements: FontMeasurements) -> Tuple[Tuple[float, float], List[float], List[float]]:
@@ -219,7 +217,9 @@ def _apply_alignment(alignment: Alignment,
         return (0, 0), [], []
 
     halign, valign = alignment
-    line_ys = [-(cap_height + i * line_spacing) for i in range(len(line_widths))]
+    line_ys = [-font_measurements.baseline -
+               (font_measurements.cap_height + i * line_spacing)
+               for i in range(len(line_widths))]
 
     if box_width is None:
         box_width = max(line_widths)
@@ -278,7 +278,7 @@ def simplified_text_chunks(text: AnyText, out: Backend,
     line_widths = [out.get_text_line_width(line, cap_height, font=font) for line in lines]
     font_measurements = out.get_font_measurements(cap_height, font=font)
     anchor, line_xs, line_ys = \
-        _apply_alignment(alignment, line_widths, cap_height, line_spacing, box_width, font_measurements)
+        _apply_alignment(alignment, line_widths, line_spacing, box_width, font_measurements)
     rotation = _get_rotation(text)
     extra_transform = _get_extra_transform(text)
     insert = _get_wcs_insert(text)


Test patch:
diff --git a/tests/test_08_addons/test_811_drawing_frontend.py b/tests/test_08_addons/test_811_drawing_frontend.py
index 9bbaf7346..0cb07db65 100644
--- a/tests/test_08_addons/test_811_drawing_frontend.py
+++ b/tests/test_08_addons/test_811_drawing_frontend.py
@@ -39,8 +39,8 @@ class BasicBackend(Backend):
 
     def get_font_measurements(self, cap_height: float,
                               font=None) -> FontMeasurements:
-        return FontMeasurements(baseline=0.0, cap_top=1.0, x_top=0.5,
-                                bottom=-0.2)
+        return FontMeasurements(baseline=0.0, cap_height=1.0, x_height=0.5,
+                                descender_height=0.2)
 
     def set_background(self, color: str) -> None:
         self.collector.append(('bgcolor', color))