diff --git a/__test__/draw.spec.ts b/__test__/draw.spec.ts index e282421d..8f153638 100644 --- a/__test__/draw.spec.ts +++ b/__test__/draw.spec.ts @@ -603,6 +603,9 @@ test('measureText with empty string should not throw', (t) => { actualBoundingBoxRight: 0, fontBoundingBoxAscent: 0, fontBoundingBoxDescent: 0, + alphabeticBaseline: 0, + emHeightAscent: 0, + emHeightDescent: 0, width: 0, }) }) diff --git a/__test__/snapshots/draw-text-maxWidth.png b/__test__/snapshots/draw-text-maxWidth.png index b4a45900..cb250e53 100644 Binary files a/__test__/snapshots/draw-text-maxWidth.png and b/__test__/snapshots/draw-text-maxWidth.png differ diff --git a/__test__/snapshots/text-baseline-all.png b/__test__/snapshots/text-baseline-all.png new file mode 100644 index 00000000..5f207246 Binary files /dev/null and b/__test__/snapshots/text-baseline-all.png differ diff --git a/__test__/snapshots/text-baseline.png b/__test__/snapshots/text-baseline.png index 41634f36..cfa430c4 100644 Binary files a/__test__/snapshots/text-baseline.png and b/__test__/snapshots/text-baseline.png differ diff --git a/__test__/text.spec.ts b/__test__/text.spec.ts index 1e9af993..9767da5e 100644 --- a/__test__/text.spec.ts +++ b/__test__/text.spec.ts @@ -67,8 +67,26 @@ test('text-baseline', async (t) => { const { ctx } = t.context ctx.font = '48px Iosevka Slab' ctx.textBaseline = 'bottom' - ctx.fillText('abcdef', 50, 50) - ctx.fillText('abcdefg', 50, 50) + ctx.fillText('abcdef', 50, 100) + ctx.fillText('abcdefg', 50, 100) + await snapshotImage(t) +}) + +test('text-baseline-all', async (t) => { + const { ctx } = t.context + const baselines = ['top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom'] as const + ctx.font = '36px Iosevka Slab' + ctx.strokeStyle = 'red' + + baselines.forEach((baseline, index) => { + ctx.textBaseline = baseline + const y = 75 + index * 75 + ctx.beginPath() + ctx.moveTo(0, y + 0.5) + ctx.lineTo(550, y + 0.5) + ctx.stroke() + ctx.fillText(`Abcdefghijklmnop (${baseline})`, 0, y) + }) await snapshotImage(t) }) diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index 7a602111..0aab8973 100644 --- a/skia-c/skia_c.cpp +++ b/skia-c/skia_c.cpp @@ -446,7 +446,7 @@ extern "C" switch (css_baseline) { case CssBaseline::Top: - baseline_offset = -alphabetic_baseline - font_metrics.fAscent; + baseline_offset = -alphabetic_baseline - font_metrics.fAscent - font_metrics.fUnderlinePosition - font_metrics.fUnderlineThickness; break; case CssBaseline::Hanging: // https://github.com/chromium/chromium/blob/104.0.5092.1/third_party/blink/renderer/core/html/canvas/text_metrics.cc#L21-L25 @@ -454,7 +454,7 @@ extern "C" // http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling // "FOP (Formatting Objects Processor) puts the hanging baseline at 80% of // the ascender height" - baseline_offset = -alphabetic_baseline - (font_metrics.fAscent - font_metrics.fDescent) * HANGING_AS_PERCENT_OF_ASCENT / 100.0; + baseline_offset = -alphabetic_baseline - font_metrics.fAscent * HANGING_AS_PERCENT_OF_ASCENT / 100.0; break; case CssBaseline::Middle: baseline_offset = -paragraph->getHeight() / 2; @@ -466,50 +466,51 @@ extern "C" baseline_offset = -paragraph->getIdeographicBaseline(); break; case CssBaseline::Bottom: - baseline_offset = font_metrics.fStrikeoutPosition; + baseline_offset = -alphabetic_baseline + font_metrics.fStrikeoutPosition + font_metrics.fStrikeoutThickness; break; }; - if (c_canvas) + auto line_center = line_width / 2.0f; + float paint_x; + switch ((TextAlign)align) { - auto line_center = line_width / 2.0f; - float paint_x; - switch ((TextAlign)align) + case TextAlign::kLeft: + paint_x = x; + break; + case TextAlign::kCenter: + paint_x = x - line_center; + break; + case TextAlign::kRight: + paint_x = x - line_width; + break; + // Unreachable + case TextAlign::kJustify: + paint_x = x; + break; + case TextAlign::kStart: + if (text_direction == TextDirection::kLtr) { - case TextAlign::kLeft: paint_x = x; - break; - case TextAlign::kCenter: - paint_x = x - line_center; - break; - case TextAlign::kRight: + } + else + { paint_x = x - line_width; - break; - // Unreachable - case TextAlign::kJustify: + } + break; + case TextAlign::kEnd: + if (text_direction == TextDirection::kRtl) + { paint_x = x; - break; - case TextAlign::kStart: - if (text_direction == TextDirection::kLtr) - { - paint_x = x; - } - else - { - paint_x = x - line_width; - } - break; - case TextAlign::kEnd: - if (text_direction == TextDirection::kRtl) - { - paint_x = x; - } - else - { - paint_x = x - line_width; - } - break; - }; + } + else + { + paint_x = x - line_width; + } + break; + }; + + if (c_canvas) + { auto need_scale = line_width > max_width; float ratio = need_scale ? max_width / line_width : 1.0; if (need_scale) @@ -529,11 +530,12 @@ extern "C" auto offset = -baseline_offset - alphabetic_baseline; c_line_metrics->ascent = -ascent + offset; c_line_metrics->descent = descent - offset; - c_line_metrics->left = line_metrics.fLeft - first_char_bounds.fLeft; - c_line_metrics->right = last_char_pos_x + last_char_bounds.fRight; + c_line_metrics->left = -paint_x + line_metrics.fLeft - first_char_bounds.fLeft; + c_line_metrics->right = paint_x + last_char_pos_x + last_char_bounds.fRight; c_line_metrics->width = line_width; c_line_metrics->font_ascent = -font_metrics.fAscent + offset; c_line_metrics->font_descent = font_metrics.fDescent - offset; + c_line_metrics->alphabetic_baseline = -font_metrics.fAscent + offset; } delete paragraph; } diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp index 38aac1fc..4af6e732 100644 --- a/skia-c/skia_c.hpp +++ b/skia-c/skia_c.hpp @@ -148,6 +148,7 @@ struct skiac_line_metrics float width; float font_ascent; float font_descent; + float alphabetic_baseline; }; struct skiac_rect diff --git a/src/ctx.rs b/src/ctx.rs index 7983cce3..ab45a075 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -1578,6 +1578,9 @@ impl CanvasRenderingContext2D { actual_bounding_box_right: 0.0, font_bounding_box_ascent: 0.0, font_bounding_box_descent: 0.0, + alphabetic_baseline: 0.0, + em_height_ascent: 0.0, + em_height_descent: 0.0, width: 0.0, }); } @@ -1589,6 +1592,9 @@ impl CanvasRenderingContext2D { actual_bounding_box_right: metrics.0.right as f64, font_bounding_box_ascent: metrics.0.font_ascent as f64, font_bounding_box_descent: metrics.0.font_descent as f64, + alphabetic_baseline: metrics.0.alphabetic_baseline as f64, + em_height_ascent: metrics.0.font_ascent as f64, + em_height_descent: metrics.0.font_descent as f64, width: metrics.0.width as f64, }) } @@ -1893,6 +1899,9 @@ pub struct TextMetrics { pub actual_bounding_box_right: f64, pub font_bounding_box_ascent: f64, pub font_bounding_box_descent: f64, + pub alphabetic_baseline: f64, + pub em_height_ascent: f64, + pub em_height_descent: f64, pub width: f64, } diff --git a/src/sk.rs b/src/sk.rs index 6a010d5a..6543ebf0 100644 --- a/src/sk.rs +++ b/src/sk.rs @@ -182,6 +182,9 @@ pub mod ffi { pub width: f32, pub font_ascent: f32, pub font_descent: f32, + pub alphabetic_baseline: f32, + pub em_height_ascent: f32, + pub em_height_descent: f32, } #[repr(C)]